Compare commits
52 Commits
a223c659a5
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
700cff0294 | ||
|
|
8ef2f0291f | ||
|
|
d5b880d70c | ||
|
|
e20ee03682 | ||
|
|
d715c0758d | ||
|
|
abc989a675 | ||
|
|
632e71707d | ||
|
|
dc757d4fcb | ||
|
|
a7ce9982c9 | ||
|
|
0841992363 | ||
|
|
6107cf85be | ||
|
|
9e54fe4b46 | ||
|
|
cd58d37ea2 | ||
|
|
fb8ff372d3 | ||
|
|
ae98225043 | ||
|
|
23a3293060 | ||
|
|
c2e47a8a1e | ||
|
|
8f00dcb5df | ||
|
|
c855877530 | ||
|
|
566cddc33f | ||
|
|
0e5e0b5668 | ||
|
|
68aa9c1df2 | ||
|
|
cdf83958c9 | ||
|
|
e7954cfb0b | ||
|
|
238214c1e9 | ||
|
|
3ea6723cf9 | ||
|
|
0a0618a680 | ||
|
|
9958306572 | ||
|
|
b244633131 | ||
|
|
a2c5d67e8a | ||
|
|
a345b81846 | ||
|
|
fb56d47b16 | ||
|
|
50cd7b57ac | ||
|
|
da254a6c8e | ||
|
|
50eac7dbd5 | ||
|
|
c408c94288 | ||
|
|
5d028a45a6 | ||
|
|
3f58bf2c01 | ||
| 5e596a4cf7 | |||
|
|
bf978f48b0 | ||
|
|
6cb9f55804 | ||
|
|
0347274af3 | ||
|
|
4fb1488c9c | ||
|
|
ebccdf5b36 | ||
|
|
a00a3b319f | ||
|
|
d5b5a7d8b0 | ||
|
|
e37a76af56 | ||
|
|
4108b04582 | ||
|
|
c2b72da849 | ||
|
|
fb3086c28e | ||
|
|
95dd467634 | ||
|
|
8c64f67538 |
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_23" default="true" project-jdk-name="23" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="25" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
70
LICENSE
70
LICENSE
@@ -1,68 +1,2 @@
|
|||||||
Open Autonomous Public License (OAPL) v1.0
|
Please read the license here: https://open-autonomous-connection.org/license.html
|
||||||
Copyright (C) 2024-2025 Open Autonomous Connection (OAC)
|
Download all third parties licenses here: https://open-autonomous-connection.org/assets/licenses.zip
|
||||||
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.
|
|
||||||
|
|||||||
25
README.MD
25
README.MD
@@ -5,30 +5,17 @@ You can easily implement this Protocol via Maven.<br />
|
|||||||
Feel free to join our Discord.
|
Feel free to join our Discord.
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
## License Notice
|
|
||||||
|
|
||||||
This project (OAC) is licensed under
|
|
||||||
the [Open Autonomous Public License (OAPL)](https://open-autonomous-connection.org/license.html).
|
|
||||||
|
|
||||||
**Third-party components:**
|
**Third-party components:**
|
||||||
|
<br />
|
||||||
|
Download all license here: https://open-autonomous-connection.org/assets/licenses.zip
|
||||||
- *UnlegitLibrary* is authored by the same copyright holder and is used here under a special agreement:
|
- *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
|
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),
|
the [GNU GPLv3](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/src/branch/master/LICENSE),
|
||||||
it is additionally licensed under OAPL **exclusively for the OAC project**.
|
it is additionally licensed under OAPL **exclusively for the OAC project**.
|
||||||
Therefore, within OAC, the OAPL terms apply to UnlegitLibrary as well.<br/>
|
Therefore, within OAC, the OAPL terms apply to UnlegitLibrary as well.
|
||||||
<br/>
|
|
||||||
### Take a look into [Certificate Generation](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/src/branch/master#certificate-generation-for-networksystem).
|
### Take a look into [Certificate Generation](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/src/branch/master#certificate-generation-for-networksystem).
|
||||||
# Bugs/Problems
|
|
||||||
- Project not tested yet
|
|
||||||
# In progress
|
|
||||||
- WebServer
|
|
||||||
# TODO
|
|
||||||
- Finishing WebServer
|
|
||||||
- Writing INS Backend and Frontend
|
|
||||||
- Writing WebClient
|
|
||||||
- Writing WebServer
|
|
||||||
- Documentation / Wiki
|
|
||||||
# Maven
|
# Maven
|
||||||
|
|
||||||
### pom.xml
|
### pom.xml
|
||||||
@@ -36,7 +23,7 @@ the [Open Autonomous Public License (OAPL)](https://open-autonomous-connection.o
|
|||||||
```
|
```
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openautonomousconnection</groupId>
|
<groupId>org.openautonomousconnection</groupId>
|
||||||
<artifactId>protocol</artifactId>
|
<artifactId>Protocol</artifactId>
|
||||||
<version>VERSION</version>
|
<version>VERSION</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|||||||
91
pom.xml
91
pom.xml
@@ -5,8 +5,8 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<groupId>org.openautonomousconnection</groupId>
|
<groupId>org.openautonomousconnection</groupId>
|
||||||
<artifactId>protocol</artifactId>
|
<artifactId>Protocol</artifactId>
|
||||||
<version>1.0.0-BETA.11</version>
|
<version>1.0.1-BETA.0.6</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,20 +15,11 @@
|
|||||||
<description>The Protocol for Server and Client</description>
|
<description>The Protocol for Server and Client</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>23</maven.compiler.source>
|
<maven.compiler.source>25</maven.compiler.source>
|
||||||
<maven.compiler.target>23</maven.compiler.target>
|
<maven.compiler.target>25</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<profiles>
|
|
||||||
<profile>
|
|
||||||
<id>github</id>
|
|
||||||
<activation>
|
|
||||||
<activeByDefault>true</activeByDefault>
|
|
||||||
</activation>
|
|
||||||
</profile>
|
|
||||||
</profiles>
|
|
||||||
|
|
||||||
<developers>
|
<developers>
|
||||||
<developer>
|
<developer>
|
||||||
<name>UnlegitDqrk</name>
|
<name>UnlegitDqrk</name>
|
||||||
@@ -65,6 +56,13 @@
|
|||||||
<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>
|
||||||
@@ -77,9 +75,8 @@
|
|||||||
|
|
||||||
<licenses>
|
<licenses>
|
||||||
<license>
|
<license>
|
||||||
<name>Open Autonomous Public License</name>
|
<name>Open Autonomous Public License (OAPL)</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>
|
||||||
|
|
||||||
@@ -87,12 +84,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>dev.unlegitdqrk</groupId>
|
<groupId>dev.unlegitdqrk</groupId>
|
||||||
<artifactId>unlegitlibrary</artifactId>
|
<artifactId>unlegitlibrary</artifactId>
|
||||||
<version>1.6.6</version>
|
<version>1.8.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<version>1.18.38</version>
|
<version>1.18.42</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -100,47 +97,40 @@
|
|||||||
<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>LATEST</version>
|
<version>1.18.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.bytebuddy</groupId>
|
<groupId>net.bytebuddy</groupId>
|
||||||
<artifactId>byte-buddy-agent</artifactId>
|
<artifactId>byte-buddy-agent</artifactId>
|
||||||
<version>LATEST</version>
|
<version>1.18.5</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>
|
||||||
@@ -158,6 +148,15 @@
|
|||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
<version>3.6.3</version>
|
<version>3.6.3</version>
|
||||||
|
<configuration>
|
||||||
|
<failOnError>false</failOnError>
|
||||||
|
<failOnWarnings>false</failOnWarnings>
|
||||||
|
<doclint>none</doclint>
|
||||||
|
<locale>en_US</locale>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
<docencoding>UTF-8</docencoding>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>attach-javadocs</id>
|
<id>attach-javadocs</id>
|
||||||
|
|||||||
@@ -1,32 +1,43 @@
|
|||||||
package org.openautonomousconnection.protocol;
|
package org.openautonomousconnection.protocol;
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.utils.Logger;
|
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
|
||||||
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||||
import org.openautonomousconnection.protocol.listeners.ClientListener;
|
import org.openautonomousconnection.protocol.listeners.ClientListener;
|
||||||
import org.openautonomousconnection.protocol.listeners.INSServerListener;
|
import org.openautonomousconnection.protocol.listeners.CustomServerListener;
|
||||||
import org.openautonomousconnection.protocol.listeners.WebServerListener;
|
|
||||||
import org.openautonomousconnection.protocol.packets.OACPacket;
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.AuthPacket;
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.AuthPacket;
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket;
|
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.INSResponsePacket;
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.UnsupportedClassicPacket;
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.classic.Classic_DomainPacket;
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.classic.Classic_MessagePacket;
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamChunkPacket_v1_0_0_B;
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.classic.Classic_PingPacket;
|
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.client.ProtocolClient;
|
||||||
import org.openautonomousconnection.protocol.side.ins.ProtocolINSServer;
|
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.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.ProtocolVersion;
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerClient;
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.ProtocolWebServer_1_0_0_B;
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerINSServer;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerWebServer;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ClientListener;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Proxy;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main bridge class for the protocol connection.
|
* The main bridge class for the protocol connection.
|
||||||
@@ -37,25 +48,7 @@ public final class ProtocolBridge {
|
|||||||
* The protocol settings for the current connection
|
* The protocol settings for the current connection
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
private final ProtocolSettings protocolSettings;
|
private final ProtocolValues protocolValues;
|
||||||
|
|
||||||
/**
|
|
||||||
* The protocol version for the current connection
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private final ProtocolVersion protocolVersion;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The logger instance for logging events and errors
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private Logger logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The protocol side instances
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private ProtocolINSServer protocolINSServer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The protocol side instances
|
* The protocol side instances
|
||||||
@@ -63,84 +56,32 @@ public final class ProtocolBridge {
|
|||||||
@Getter
|
@Getter
|
||||||
private ProtocolClient protocolClient;
|
private ProtocolClient protocolClient;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The protocol side instances
|
* The protocol side instances
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
private ProtocolWebServer protocolWebServer;
|
private ProtocolCustomServer protocolServer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The classic protocol handlers for INS server side
|
* Initialize the ProtocolBridge instance for the client side
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private ClassicHandlerINSServer classicHandlerINSServer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The classic protocol handlers for web server side
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private ClassicHandlerWebServer classicHandlerWebServer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The classic protocol handlers for client side
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private ClassicHandlerClient classicHandlerClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The proxy for client side
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private Proxy proxy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the ProtocolBridge instance for the INS server side
|
|
||||||
*
|
*
|
||||||
* @param protocolINSServer The ProtocolINSServer instance
|
* @param protocolServer The ProtocolCustomServer instance
|
||||||
* @param protocolSettings The ProtocolSettings instance
|
* @param protocolValues The ProtocolSettings instance
|
||||||
* @param protocolVersion The ProtocolVersion instance
|
|
||||||
* @param logFolder The folder to store the log files
|
|
||||||
* @throws Exception if an error occurs while initializing the ProtocolBridge
|
* @throws Exception if an error occurs while initializing the ProtocolBridge
|
||||||
*/
|
*/
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
|
public ProtocolBridge(ProtocolCustomServer protocolServer, ProtocolValues protocolValues) throws Exception {
|
||||||
public ProtocolBridge(ProtocolINSServer protocolINSServer, ProtocolSettings protocolSettings, ProtocolVersion protocolVersion, File logFolder) throws Exception {
|
|
||||||
// Assign the parameters to the class fields
|
// Assign the parameters to the class fields
|
||||||
this.protocolINSServer = protocolINSServer;
|
this.protocolServer = protocolServer;
|
||||||
this.protocolSettings = protocolSettings;
|
this.protocolValues = protocolValues;
|
||||||
this.protocolVersion = protocolVersion;
|
|
||||||
|
if (protocolServer instanceof ProtocolINSServer)
|
||||||
|
protocolServer.attachBridge(this, null, false, ClientAuthMode.NONE);
|
||||||
|
else
|
||||||
|
protocolServer.attachBridge(this, protocolValues.keyPass, protocolValues.ssl, protocolValues.authMode);
|
||||||
|
|
||||||
// Initialize the logger and protocol version
|
|
||||||
initializeLogger(logFolder);
|
|
||||||
initializeProtocolVersion();
|
|
||||||
|
|
||||||
// Register the appropriate listeners and packets
|
|
||||||
registerListeners();
|
|
||||||
registerPackets();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the ProtocolBridge instance for the web server side
|
|
||||||
*
|
|
||||||
* @param protocolWebServer The ProtocolWebServer instance
|
|
||||||
* @param protocolSettings The ProtocolSettings instance
|
|
||||||
* @param protocolVersion The ProtocolVersion instance
|
|
||||||
* @param logFolder The folder to store the log files
|
|
||||||
* @throws Exception if an error occurs while initializing the ProtocolBridge
|
|
||||||
*/
|
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
|
||||||
public ProtocolBridge(ProtocolWebServer protocolWebServer, ProtocolSettings protocolSettings, ProtocolVersion protocolVersion, File logFolder) throws Exception {
|
|
||||||
// Assign the parameters to the class fields
|
|
||||||
this.protocolWebServer = protocolWebServer;
|
|
||||||
this.protocolSettings = protocolSettings;
|
|
||||||
this.protocolVersion = protocolVersion;
|
|
||||||
|
|
||||||
// Initialize the logger and protocol version
|
|
||||||
initializeLogger(logFolder);
|
|
||||||
initializeProtocolVersion();
|
initializeProtocolVersion();
|
||||||
|
downloadLicenses();
|
||||||
|
|
||||||
// Register the appropriate listeners and packets
|
// Register the appropriate listeners and packets
|
||||||
registerListeners();
|
registerListeners();
|
||||||
@@ -150,54 +91,109 @@ public final class ProtocolBridge {
|
|||||||
/**
|
/**
|
||||||
* Initialize the ProtocolBridge instance for the client side
|
* Initialize the ProtocolBridge instance for the client side
|
||||||
*
|
*
|
||||||
* @param protocolClient The ProtocolClient instance
|
* @param protocolClient The ProtocolClient instance
|
||||||
* @param protocolSettings The ProtocolSettings instance
|
* @param protocolValues The ProtocolSettings instance
|
||||||
* @param protocolVersion The ProtocolVersion instance
|
|
||||||
* @param logFolder The folder to store the log files
|
|
||||||
* @throws Exception if an error occurs while initializing the ProtocolBridge
|
* @throws Exception if an error occurs while initializing the ProtocolBridge
|
||||||
*/
|
*/
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.CLIENT)
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.CLIENT)
|
||||||
public ProtocolBridge(ProtocolClient protocolClient, ProtocolSettings protocolSettings, ProtocolVersion protocolVersion, File logFolder) throws Exception {
|
public ProtocolBridge(ProtocolClient protocolClient, LibClientImpl_v1_0_1_B libClientImpl,
|
||||||
|
ProtocolValues protocolValues) throws Exception {
|
||||||
// Assign the parameters to the class fields
|
// Assign the parameters to the class fields
|
||||||
this.protocolClient = protocolClient;
|
this.protocolClient = protocolClient;
|
||||||
this.protocolSettings = protocolSettings;
|
this.protocolValues = protocolValues;
|
||||||
this.protocolVersion = protocolVersion;
|
|
||||||
|
|
||||||
// Initialize the logger and protocol version
|
protocolClient.attachBridge(this);
|
||||||
initializeLogger(logFolder);
|
|
||||||
initializeProtocolVersion();
|
initializeProtocolVersion();
|
||||||
|
downloadLicenses();
|
||||||
|
|
||||||
|
|
||||||
// Register the appropriate listeners and packets
|
// Register the appropriate listeners and packets
|
||||||
registerListeners();
|
registerListeners();
|
||||||
registerPackets();
|
registerPackets();
|
||||||
|
installUrl(libClientImpl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void installUrl(LibClientImpl_v1_0_1_B libClientImpl) {
|
||||||
|
if (protocolValues.protocolVersion == ProtocolVersion.PV_1_0_0_BETA) {
|
||||||
|
OacWebUrlInstaller_v1_0_0_B.installOnce(this, libClientImpl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protocolValues.protocolVersion == ProtocolVersion.PV_1_0_1_BETA) {
|
||||||
|
OacUrlHandlerInstaller_v1_0_1_B.installOnce(this, libClientImpl, libClientImpl, libClientImpl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadLicenses() throws IOException {
|
||||||
|
File licensesFolder = new File("licenses");
|
||||||
|
|
||||||
|
if (!licensesFolder.exists() || !licensesFolder.isDirectory()) {
|
||||||
|
if (licensesFolder.exists()) licensesFolder.delete();
|
||||||
|
|
||||||
|
File output = new File("licenses.zip");
|
||||||
|
output.createNewFile();
|
||||||
|
FileUtils.downloadFile("https://open-autonomous-connection.org/assets/licenses.zip", output);
|
||||||
|
FileUtils.unzip(output, licensesFolder.getParent());
|
||||||
|
output.deleteOnExit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the appropriate packets based on the current protocol version
|
* Register the appropriate packets
|
||||||
|
*
|
||||||
|
* <p>Overview of all Packets
|
||||||
|
*
|
||||||
|
* <table border="2">
|
||||||
|
* <tr><th>ID</th><th>Packet</th><th>ProtocolVersion</th></tr>
|
||||||
|
*
|
||||||
|
* <tr><td>8</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.AuthPacket AuthPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
|
||||||
|
* <tr><td>7</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket INSQueryPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
|
||||||
|
* <tr><td>6</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSResponsePacket INSResponsePacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
|
||||||
|
* <tr><td>10</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket WebRequestPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
|
||||||
|
* <tr><td>9</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket WebResponsePacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
|
||||||
|
* <tr><td>11</td><td>{@link WebStreamStartPacket_v1_0_0_B v1_0_0.beta.web.stream.WebStreamStartPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
|
||||||
|
* <tr><td>13</td><td>{@link WebStreamChunkPacket_v1_0_0_B v1_0_0.beta.web.stream.WebStreamChunkPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
|
||||||
|
* <tr><td>12</td><td>{@link WebStreamEndPacket_v1_0_0_B v1_0_0.beta.web.stream.WebStreamEndPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
|
||||||
|
*
|
||||||
|
* <tr><td>1</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateRequestPacket WebNavigateRequestPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>2</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateAckPacket WebNavigateAckPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>3</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket WebResourceRequestPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>4</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket WebResourceResponsePacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>14</td><td>{@link WebStreamStartPacket_v1_0_1_B v1_0_1.beta.web.impl.stream.WebStreamStartPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>15</td><td>{@link WebStreamChunkPacket_v1_0_1_B v1_0_1.beta.web.impl.stream.WebStreamChunkPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>16</td><td>{@link WebStreamEndPacket_v1_0_1_B v1_0_1.beta.web.impl.stream.WebStreamEndPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>17</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentSnapshotEventPacket WebDocumentSnapshotEventPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>5</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyResponsePacket WebDocumentApplyResponsePacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>18</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyRequestPacket WebDocumentApplyRequestPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* </table>
|
||||||
|
*
|
||||||
|
* @see org.openautonomousconnection.protocol.versions.ProtocolVersion
|
||||||
*/
|
*/
|
||||||
private void registerPackets() {
|
private void registerPackets() {
|
||||||
// Classic packets
|
|
||||||
Classic_DomainPacket cDomainPacket = new Classic_DomainPacket();
|
|
||||||
Classic_MessagePacket cMessagePacket = new Classic_MessagePacket();
|
|
||||||
Classic_PingPacket cPingPacket = new Classic_PingPacket();
|
|
||||||
|
|
||||||
if (isPacketSupported(cDomainPacket)) protocolSettings.packetHandler.registerPacket(cDomainPacket);
|
|
||||||
if (isPacketSupported(cMessagePacket)) protocolSettings.packetHandler.registerPacket(cMessagePacket);
|
|
||||||
if (isPacketSupported(cPingPacket)) protocolSettings.packetHandler.registerPacket(cPingPacket);
|
|
||||||
|
|
||||||
// 1.0.0-BETA packets
|
// 1.0.0-BETA packets
|
||||||
AuthPacket v100bAuthPath = new AuthPacket();
|
registerPacket(() -> new AuthPacket(this));
|
||||||
UnsupportedClassicPacket v100bUnsupportedClassicPacket = new UnsupportedClassicPacket();
|
registerPacket(INSQueryPacket::new);
|
||||||
INSQueryPacket v100BINSQueryPacket = new INSQueryPacket();
|
registerPacket(() -> new INSResponsePacket(this));
|
||||||
INSResponsePacket v100BINSResponsePacket = new INSResponsePacket(this);
|
registerPacket(WebRequestPacket::new);
|
||||||
|
registerPacket(WebResponsePacket::new);
|
||||||
|
registerPacket(WebStreamStartPacket_v1_0_0_B::new);
|
||||||
|
registerPacket(WebStreamChunkPacket_v1_0_0_B::new);
|
||||||
|
registerPacket(WebStreamEndPacket_v1_0_0_B::new);
|
||||||
|
|
||||||
if (isPacketSupported(v100bAuthPath)) protocolSettings.packetHandler.registerPacket(v100bAuthPath);
|
// 1.0.1-BETA Packets
|
||||||
if (isPacketSupported(v100bUnsupportedClassicPacket))
|
registerPacket(() -> new WebDocumentApplyRequestPacket(this));
|
||||||
protocolSettings.packetHandler.registerPacket(v100bUnsupportedClassicPacket);
|
registerPacket(WebDocumentApplyResponsePacket::new);
|
||||||
if (isPacketSupported(v100BINSQueryPacket))
|
registerPacket(WebDocumentSnapshotEventPacket::new);
|
||||||
protocolSettings.packetHandler.registerPacket(v100BINSQueryPacket);
|
registerPacket(() -> new WebNavigateRequestPacket(this));
|
||||||
if (isPacketSupported(v100BINSResponsePacket))
|
registerPacket(WebNavigateAckPacket::new);
|
||||||
protocolSettings.packetHandler.registerPacket(v100BINSResponsePacket);
|
registerPacket(() -> new WebResourceRequestPacket(this));
|
||||||
|
registerPacket(WebResourceResponsePacket::new);
|
||||||
|
registerPacket(WebStreamStartPacket_v1_0_1_B::new);
|
||||||
|
registerPacket(WebStreamChunkPacket_v1_0_1_B::new);
|
||||||
|
registerPacket(WebStreamEndPacket_v1_0_1_B::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerPacket(Supplier<? extends OACPacket> factory) {
|
||||||
|
if (isPacketSupported(factory.get())) protocolValues.packetHandler.registerPacket(factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -206,60 +202,17 @@ public final class ProtocolBridge {
|
|||||||
* @throws Exception if an error occurs while registering the listeners
|
* @throws Exception if an error occurs while registering the listeners
|
||||||
*/
|
*/
|
||||||
private void registerListeners() throws Exception {
|
private void registerListeners() throws Exception {
|
||||||
// Classic listeners
|
|
||||||
if (isClassicSupported()) {
|
|
||||||
Classic_ClientListener classicListener = new Classic_ClientListener();
|
|
||||||
classicListener.setProtocolBridge(this);
|
|
||||||
protocolSettings.eventManager.registerListener(classicListener.getClass());
|
|
||||||
} else protocolSettings.eventManager.unregisterListener(Classic_ClientListener.class);
|
|
||||||
|
|
||||||
// INS Listeners
|
|
||||||
if (isRunningAsINSServer()) {
|
|
||||||
INSServerListener serverListener = new INSServerListener();
|
|
||||||
serverListener.setINSServer(protocolINSServer);
|
|
||||||
protocolSettings.eventManager.registerListener(serverListener.getClass());
|
|
||||||
protocolSettings.eventManager.unregisterListener(WebServerListener.class);
|
|
||||||
protocolSettings.eventManager.unregisterListener(ClientListener.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Web Listeners
|
|
||||||
if (isRunningAsWebServer()) {
|
|
||||||
WebServerListener serverListener = new WebServerListener();
|
|
||||||
serverListener.setWebServer(protocolWebServer);
|
|
||||||
protocolSettings.eventManager.registerListener(serverListener.getClass());
|
|
||||||
protocolSettings.eventManager.unregisterListener(INSServerListener.class);
|
|
||||||
protocolSettings.eventManager.unregisterListener(ClientListener.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client Listeners
|
// Client Listeners
|
||||||
if (isRunningAsClient()) {
|
if (isRunningAsClient()) {
|
||||||
ClientListener clientListener = new ClientListener();
|
protocolValues.eventManager.registerListener(new ClientListener(protocolClient));
|
||||||
clientListener.setClient(protocolClient);
|
protocolValues.eventManager.unregisterListener(CustomServerListener.class);
|
||||||
protocolSettings.eventManager.registerListener(clientListener.getClass());
|
|
||||||
protocolSettings.eventManager.unregisterListener(INSServerListener.class);
|
|
||||||
protocolSettings.eventManager.unregisterListener(WebServerListener.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the logger instance
|
|
||||||
*
|
|
||||||
* @param logFolder The folder to store the log files
|
|
||||||
*/
|
|
||||||
private void initializeLogger(File logFolder) {
|
|
||||||
// Create a temporary logger instance to avoid final field issues
|
|
||||||
Logger tmpLogger = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Initialize temporary logger
|
|
||||||
tmpLogger = new Logger(logFolder, false, true);
|
|
||||||
} catch (IOException | NoSuchFieldException | IllegalAccessException exception) {
|
|
||||||
exception.printStackTrace();
|
|
||||||
System.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign the temporary logger to the final field
|
// Server Listeners
|
||||||
this.logger = tmpLogger;
|
if (isRunningAsServer()) {
|
||||||
|
protocolValues.eventManager.registerListener(new CustomServerListener(protocolServer));
|
||||||
|
protocolValues.eventManager.unregisterListener(ClientListener.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -271,7 +224,7 @@ public final class ProtocolBridge {
|
|||||||
// Check if the protocol version is valid for the current side
|
// Check if the protocol version is valid for the current side
|
||||||
// If not, log an error and exit the application
|
// If not, log an error and exit the application
|
||||||
if (!validateProtocolSide()) {
|
if (!validateProtocolSide()) {
|
||||||
this.logger.error("Invalid protocol version '" + protocolVersion.toString() + "'!");
|
protocolValues.logger.error("Invalid protocol version '" + protocolValues.protocolVersion.toString() + "'!");
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,14 +236,14 @@ public final class ProtocolBridge {
|
|||||||
*/
|
*/
|
||||||
public boolean isClassicSupported() {
|
public boolean isClassicSupported() {
|
||||||
boolean yes = false;
|
boolean yes = false;
|
||||||
for (ProtocolVersion compatibleVersion : protocolVersion.getCompatibleVersions()) {
|
for (ProtocolVersion compatibleVersion : protocolValues.protocolVersion.getCompatibleVersions()) {
|
||||||
// Check if the compatible version is classic
|
// Check if the compatible version is classic
|
||||||
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
||||||
if (yes) break;
|
if (yes) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the current protocol version is classic or if it is supported by any of the compatible versions
|
// Check if the current protocol version is classic or if it is supported by any of the compatible versions
|
||||||
return protocolVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC || yes;
|
return protocolValues.protocolVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC || yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -301,14 +254,15 @@ public final class ProtocolBridge {
|
|||||||
*/
|
*/
|
||||||
public boolean isProtocolSupported(ProtocolVersion.Protocol protocol) {
|
public boolean isProtocolSupported(ProtocolVersion.Protocol protocol) {
|
||||||
boolean yes = false;
|
boolean yes = false;
|
||||||
for (ProtocolVersion compatibleVersion : protocolVersion.getCompatibleVersions()) {
|
|
||||||
|
for (ProtocolVersion compatibleVersion : protocolValues.protocolVersion.getCompatibleVersions()) {
|
||||||
// Check if the compatible version supports the target protocol
|
// Check if the compatible version supports the target protocol
|
||||||
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
|
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
|
||||||
if (yes) break;
|
if (yes) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the current protocol version supports the target protocol or if it is supported by any of the compatible versions
|
// Check if the current protocol version supports the target protocol or if it is supported by any of the compatible versions
|
||||||
return protocolVersion.getSupportedProtocols().contains(protocol) || yes;
|
return protocolValues.protocolVersion.getSupportedProtocols().contains(protocol) || yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -318,7 +272,13 @@ public final class ProtocolBridge {
|
|||||||
* @return true if the target packet is supported, false otherwise
|
* @return true if the target packet is supported, false otherwise
|
||||||
*/
|
*/
|
||||||
public boolean isPacketSupported(OACPacket packet) {
|
public boolean isPacketSupported(OACPacket packet) {
|
||||||
return isVersionSupported(packet.getProtocolVersion());
|
boolean compatible = false;
|
||||||
|
|
||||||
|
for (ProtocolVersion compatibleVersion : packet.getCompatibleVersions()) {
|
||||||
|
if (!compatible) compatible = isVersionSupported(compatibleVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
return compatible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -329,7 +289,7 @@ public final class ProtocolBridge {
|
|||||||
*/
|
*/
|
||||||
public boolean isVersionSupported(ProtocolVersion targetVersion) {
|
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
|
// Check if the target protocol version is the same as the current protocol version or if it is in the list of compatible versions
|
||||||
return protocolVersion == targetVersion || protocolVersion.getCompatibleVersions().contains(targetVersion);
|
return protocolValues.protocolVersion == targetVersion || protocolValues.protocolVersion.getCompatibleVersions().contains(targetVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -339,20 +299,22 @@ public final class ProtocolBridge {
|
|||||||
*/
|
*/
|
||||||
private boolean validateProtocolSide() {
|
private boolean validateProtocolSide() {
|
||||||
return
|
return
|
||||||
(isRunningAsClient() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT) ||
|
(isRunningAsClient() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT) ||
|
||||||
(isRunningAsClient() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_WEB) ||
|
(isRunningAsClient() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_WEB) ||
|
||||||
(isRunningAsClient() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_INS) ||
|
(isRunningAsClient() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_INS) ||
|
||||||
(isRunningAsClient() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL) ||
|
(isRunningAsClient() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL) ||
|
||||||
|
|
||||||
(isRunningAsWebServer() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.WEB) ||
|
(isRunningAsWebServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.WEB) ||
|
||||||
(isRunningAsWebServer() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_WEB) ||
|
(isRunningAsWebServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_WEB) ||
|
||||||
(isRunningAsWebServer() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.WEB_INS) ||
|
(isRunningAsWebServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.WEB_INS) ||
|
||||||
(isRunningAsWebServer() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL) ||
|
(isRunningAsWebServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL) ||
|
||||||
|
|
||||||
(isRunningAsINSServer() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.INS) ||
|
(isRunningAsServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL) ||
|
||||||
(isRunningAsINSServer() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.WEB_INS) ||
|
|
||||||
(isRunningAsINSServer() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_INS) ||
|
(isRunningAsINSServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.INS) ||
|
||||||
(isRunningAsINSServer() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL);
|
(isRunningAsINSServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.WEB_INS) ||
|
||||||
|
(isRunningAsINSServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_INS) ||
|
||||||
|
(isRunningAsINSServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -361,7 +323,7 @@ public final class ProtocolBridge {
|
|||||||
* @return true if the current instance is running as a INS server, false otherwise
|
* @return true if the current instance is running as a INS server, false otherwise
|
||||||
*/
|
*/
|
||||||
public boolean isRunningAsINSServer() {
|
public boolean isRunningAsINSServer() {
|
||||||
return protocolINSServer != null;
|
return isRunningAsServer() && protocolServer instanceof ProtocolINSServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -379,6 +341,17 @@ public final class ProtocolBridge {
|
|||||||
* @return true if the current instance is running as a web server, false otherwise
|
* @return true if the current instance is running as a web server, false otherwise
|
||||||
*/
|
*/
|
||||||
public boolean isRunningAsWebServer() {
|
public boolean isRunningAsWebServer() {
|
||||||
return protocolWebServer != null;
|
if (protocolValues.protocolVersion == ProtocolVersion.PV_1_0_0_BETA)
|
||||||
|
return isRunningAsServer() && protocolServer instanceof ProtocolWebServer_1_0_0_B;
|
||||||
|
return isRunningAsServer() && protocolServer instanceof ProtocolWebServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current instance is running as a custom server
|
||||||
|
*
|
||||||
|
* @return true if the current instance is running as a custom server, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isRunningAsServer() {
|
||||||
|
return protocolServer != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Settings for the protocol connection.
|
|
||||||
*/
|
|
||||||
public final class ProtocolSettings extends DefaultMethodsOverrider {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The host to connect to.
|
|
||||||
*/
|
|
||||||
public String host;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The port to connect to.
|
|
||||||
*/
|
|
||||||
public int port;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The protocol version to use.
|
|
||||||
*/
|
|
||||||
public PacketHandler packetHandler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The event manager to use.
|
|
||||||
*/
|
|
||||||
public EventManager eventManager;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.openautonomousconnection.protocol;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.addon.AddonLoader;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.utils.Logger;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Settings for the protocol connection.
|
||||||
|
*/
|
||||||
|
public final class ProtocolValues extends DefaultMethodsOverrider {
|
||||||
|
/**
|
||||||
|
* The packet handler to use.
|
||||||
|
*/
|
||||||
|
public PacketHandler packetHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The event manager to use.
|
||||||
|
*/
|
||||||
|
public EventManager eventManager;
|
||||||
|
|
||||||
|
public AddonLoader addonLoader;
|
||||||
|
|
||||||
|
public Logger logger;
|
||||||
|
|
||||||
|
public String keyPass = null;
|
||||||
|
|
||||||
|
public ProtocolVersion protocolVersion;
|
||||||
|
|
||||||
|
public boolean ssl = true;
|
||||||
|
public ClientAuthMode authMode = ClientAuthMode.NONE;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
/**
|
/**
|
||||||
* Annotation to provide metadata about protocol handlers or classes.
|
* Annotation to provide metadata about protocol handlers or classes.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.5")
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface ProtocolInfo {
|
public @interface ProtocolInfo {
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import net.bytebuddy.agent.ByteBuddyAgent;
|
|||||||
import net.bytebuddy.agent.builder.AgentBuilder;
|
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||||
import net.bytebuddy.asm.Advice;
|
import net.bytebuddy.asm.Advice;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.instrument.Instrumentation;
|
import java.lang.instrument.Instrumentation;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
@@ -73,6 +72,6 @@ public class CallTracker<A extends Annotation> extends GenericReflectClass<A> {
|
|||||||
/**
|
/**
|
||||||
* Code executed on any method call
|
* Code executed on any method call
|
||||||
*/
|
*/
|
||||||
public abstract void onCall(Method method, @Nullable StackTraceElement callerMethod);
|
public abstract void onCall(Method method, StackTraceElement callerMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
|||||||
import org.openautonomousconnection.protocol.exceptions.IncompatibleProtocolSideException;
|
import org.openautonomousconnection.protocol.exceptions.IncompatibleProtocolSideException;
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
@@ -36,7 +35,7 @@ public class ProtocolInfoProcessing extends AnnotationProcessor<ProtocolInfo> {
|
|||||||
|
|
||||||
this.tracker = new CallTracker<>(new CallTracker.CallInterceptor() {
|
this.tracker = new CallTracker<>(new CallTracker.CallInterceptor() {
|
||||||
@Override
|
@Override
|
||||||
public void onCall(Method method, @Nullable StackTraceElement callerMethod) {
|
public void onCall(Method method, StackTraceElement callerMethod) {
|
||||||
ProtocolVersion.ProtocolSide side, callerSide;
|
ProtocolVersion.ProtocolSide side, callerSide;
|
||||||
Object o;
|
Object o;
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
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.ClientConnectedEvent;
|
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientConnectedEvent;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.ClientDisconnectedEvent;
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.AuthPacket;
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.AuthPacket;
|
||||||
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
|
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener for client-side events such as connection and disconnection.
|
* Listener for client-side events such as connection and disconnection.
|
||||||
*/
|
*/
|
||||||
@@ -22,15 +21,14 @@ public final class ClientListener extends EventListener {
|
|||||||
* The reference to the ProtocolClient object
|
* The reference to the ProtocolClient object
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
private ProtocolClient client;
|
private final ProtocolClient client;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the client variable
|
* Sets the client variable
|
||||||
*
|
*
|
||||||
* @param client The Instance of the ProtocolClient
|
* @param client The Instance of the ProtocolClient
|
||||||
*/
|
*/
|
||||||
public void setClient(ProtocolClient client) {
|
public ClientListener(ProtocolClient client) {
|
||||||
if (this.client != null) return;
|
|
||||||
this.client = client;
|
this.client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,25 +38,14 @@ public final class ClientListener extends EventListener {
|
|||||||
*
|
*
|
||||||
* @param event The client connected event.
|
* @param event The client connected event.
|
||||||
*/
|
*/
|
||||||
@Listener
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
public void onConnect(ClientConnectedEvent event) {
|
public void onConnect(ClientConnectedEvent event) {
|
||||||
try {
|
try {
|
||||||
event.getClient().sendPacket(new AuthPacket(client.getProtocolBridge()));
|
event.getClient().sendPacket(new AuthPacket(client.getProtocolBridge()), TransportProtocol.TCP);
|
||||||
} catch (IOException | ClassNotFoundException exception) {
|
} catch (Exception exception) {
|
||||||
event.getClient().getLogger().exception("Failed to send auth packet", exception);
|
client.getProtocolBridge().getProtocolValues().logger.exception("Failed to send auth packet", exception);
|
||||||
event.getClient().disconnect();
|
event.getClient().disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the event when a client disconnects.
|
|
||||||
* Notifies the protocol client of the disconnection.
|
|
||||||
*
|
|
||||||
* @param event The client disconnected event.
|
|
||||||
*/
|
|
||||||
@Listener
|
|
||||||
public void onDisconnect(ClientDisconnectedEvent event) {
|
|
||||||
client.onINSDisconnect(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package org.openautonomousconnection.protocol.listeners;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.client.S_ClientConnectedEvent;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.S_PacketReadEvent;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.side.ins.ProtocolINSServer;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.ProtocolCustomServer;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.ProtocolWebServer_1_0_0_B;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener for web server connection events.
|
||||||
|
*/
|
||||||
|
public final class CustomServerListener extends EventListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reference to the CustomProtocolServer object
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final ProtocolCustomServer server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the webServer variable
|
||||||
|
*
|
||||||
|
* @param server The Instance of the CustomProtocolServer
|
||||||
|
*/
|
||||||
|
public CustomServerListener(ProtocolCustomServer server) {
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the event when a connection is established.
|
||||||
|
* Adds the connected client to the protocol web server's client list.
|
||||||
|
*
|
||||||
|
* @param event The connection handler connected event.
|
||||||
|
*/
|
||||||
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
|
public void onConnect(S_ClientConnectedEvent event) {
|
||||||
|
try {
|
||||||
|
server.getClients().add(new CustomConnectedClient(event.getClient(), server));
|
||||||
|
} catch (Exception e) {
|
||||||
|
server.getProtocolBridge().getProtocolValues().logger.exception("Failed to add client to server", e);
|
||||||
|
event.getClient().disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
|
public void onPacketWeb(S_PacketReadEvent event) {
|
||||||
|
if (!server.getProtocolBridge().isRunningAsWebServer()) return;
|
||||||
|
if (event.getPacket() instanceof WebRequestPacket) {
|
||||||
|
try {
|
||||||
|
event.getClient().sendPacket(
|
||||||
|
((ProtocolWebServer_1_0_0_B) server.getProtocolBridge().getProtocolServer()).
|
||||||
|
onWebRequest(server.getClientByID(event.getClient().getUniqueID()), (WebRequestPacket) event.getPacket()),
|
||||||
|
TransportProtocol.TCP);
|
||||||
|
} catch (IOException e) {
|
||||||
|
server.getProtocolBridge().getProtocolValues().logger.exception("Failed to send web response", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles incoming INS query packets from connected clients.
|
||||||
|
* <p>
|
||||||
|
* This method performs the following steps:
|
||||||
|
* <ul>
|
||||||
|
* <li>Checks whether the received packet is an {@link INSQueryPacket}</li>
|
||||||
|
* <li>Notifies the INS server through {@code onQueryReceived}</li>
|
||||||
|
* <li>Resolves the request via {@code resolve()}</li>
|
||||||
|
* <li>Wraps the result in an {@link INSResponsePacket}</li>
|
||||||
|
* <li>Sends the response back to the requesting client</li>
|
||||||
|
* </ul>
|
||||||
|
* If sending the response fails, the INS server receives an
|
||||||
|
* {@code onResponseSentFailed(...)} callback.
|
||||||
|
*
|
||||||
|
* @param event The packet event received by the network system.
|
||||||
|
*/
|
||||||
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
|
public void onPacketINS(S_PacketReadEvent event) {
|
||||||
|
if (!(event.getPacket() instanceof INSQueryPacket q)) return;
|
||||||
|
if (!server.getProtocolBridge().isRunningAsINSServer()) return;
|
||||||
|
|
||||||
|
ProtocolINSServer insServer = (ProtocolINSServer) server;
|
||||||
|
|
||||||
|
insServer.onQueryReceived(q.getTLN(), q.getName(), q.getSub(), q.getType());
|
||||||
|
List<INSRecord> resolved = new ArrayList<>();
|
||||||
|
INSResponseStatus status = null;
|
||||||
|
|
||||||
|
if (q.getSub() == null && q.getTLN().equalsIgnoreCase("oac")) {
|
||||||
|
if (q.getName().equalsIgnoreCase("info")) {
|
||||||
|
// Return INS server info site
|
||||||
|
String insInfo = insServer.getInsInfoSite();
|
||||||
|
if (!insInfo.contains(":")) insInfo = insInfo + ":1028";
|
||||||
|
|
||||||
|
String[] hostPort = insInfo.split(":");
|
||||||
|
resolved = List.of(new INSRecord(q.getType(), hostPort[0], -1, -1, Integer.parseInt(hostPort[1]), 0));
|
||||||
|
} else if (q.getName().equalsIgnoreCase("register")) {
|
||||||
|
// Return INS frontend site
|
||||||
|
String insFrontend = insServer.getInsFrontendSite();
|
||||||
|
if (!insFrontend.contains(":")) insFrontend = insFrontend + ":1028";
|
||||||
|
|
||||||
|
String[] hostPort = insFrontend.split(":");
|
||||||
|
resolved = List.of(new INSRecord(q.getType(), hostPort[0], -1, -1, Integer.parseInt(hostPort[1]), 0));
|
||||||
|
} else {
|
||||||
|
// Not a special name → use normal resolving
|
||||||
|
resolved = insServer.resolve(q.getTLN(), q.getName(), q.getName(), q.getType());
|
||||||
|
}
|
||||||
|
} else if (q.getSub() == null && q.getName().equalsIgnoreCase("info")) {
|
||||||
|
// Return TLN server info site
|
||||||
|
String resolve = insServer.resolveTLNInfoSite(q.getTLN());
|
||||||
|
if (resolve == null) status = INSResponseStatus.INVALID_REQUEST;
|
||||||
|
else {
|
||||||
|
if (!resolve.contains(":")) resolve = resolve + ":1028";
|
||||||
|
String[] hostPort = resolve.split(":");
|
||||||
|
resolved = List.of(new INSRecord(q.getType(), hostPort[0], -1, -1, Integer.parseInt(hostPort[1]), 0));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Normal resolving
|
||||||
|
resolved = insServer.resolve(q.getTLN(), q.getName(), q.getSub(), q.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
status = status == null && resolved.isEmpty() ? INSResponseStatus.NOT_FOUND : INSResponseStatus.OK;
|
||||||
|
INSResponsePacket response = new INSResponsePacket(status, resolved, q.getClientId(), insServer.getProtocolBridge());
|
||||||
|
|
||||||
|
try {
|
||||||
|
event.getClient().sendPacket(response, TransportProtocol.TCP);
|
||||||
|
insServer.onResponseSent(q.getTLN(), q.getName(), q.getSub(), q.getType(), resolved);
|
||||||
|
} catch (Exception e) {
|
||||||
|
insServer.onResponseSentFailed(q.getTLN(), q.getName(), q.getSub(), q.getType(), resolved, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.listeners;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.ConnectionHandlerConnectedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.ConnectionHandlerDisconnectedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.S_PacketReceivedEvent;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket;
|
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSResponsePacket;
|
|
||||||
import org.openautonomousconnection.protocol.side.ins.ConnectedProtocolClient;
|
|
||||||
import org.openautonomousconnection.protocol.side.ins.ProtocolINSServer;
|
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener for INS server connection events.
|
|
||||||
*/
|
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
|
|
||||||
public final class INSServerListener extends EventListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The reference to the INSServer object
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private ProtocolINSServer insServer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the insServer variable
|
|
||||||
*
|
|
||||||
* @param insServer The Instance of the INSServer
|
|
||||||
*/
|
|
||||||
public void setINSServer(ProtocolINSServer insServer) {
|
|
||||||
if (this.insServer != null) return;
|
|
||||||
this.insServer = insServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the event when a connection handler connects to the INS server.
|
|
||||||
* Adds the connected client to the ProtocolBridge's INS server client list.
|
|
||||||
*
|
|
||||||
* @param event The connection handler connected event.
|
|
||||||
*/
|
|
||||||
@Listener
|
|
||||||
public void onConnect(ConnectionHandlerConnectedEvent event) {
|
|
||||||
insServer.getClients().add(new ConnectedProtocolClient(event.getConnectionHandler(), insServer));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the event when a connection handler disconnects from the INS server.
|
|
||||||
* Removes the disconnected client from the ProtocolBridge's INS server client list.
|
|
||||||
*
|
|
||||||
* @param event The connection handler disconnected event.
|
|
||||||
*/
|
|
||||||
@Listener
|
|
||||||
public void onDisconnect(ConnectionHandlerDisconnectedEvent event) {
|
|
||||||
insServer.getClients().removeIf(client -> client.getConnectionHandler().getClientID() == -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles incoming INS query packets from connected clients.
|
|
||||||
* <p>
|
|
||||||
* This method performs the following steps:
|
|
||||||
* <ul>
|
|
||||||
* <li>Checks whether the received packet is an {@link INSQueryPacket}</li>
|
|
||||||
* <li>Notifies the INS server through {@code onQueryReceived}</li>
|
|
||||||
* <li>Resolves the request via {@code resolve()}</li>
|
|
||||||
* <li>Wraps the result in an {@link INSResponsePacket}</li>
|
|
||||||
* <li>Sends the response back to the requesting client</li>
|
|
||||||
* </ul>
|
|
||||||
* If sending the response fails, the INS server receives an
|
|
||||||
* {@code onResponseSentFailed(...)} callback.
|
|
||||||
*
|
|
||||||
* @param event The packet event received by the network system.
|
|
||||||
*/
|
|
||||||
@Listener
|
|
||||||
public void onPacket(S_PacketReceivedEvent event) {
|
|
||||||
if (!(event.getPacket() instanceof INSQueryPacket q)) return;
|
|
||||||
|
|
||||||
insServer.onQueryReceived(q.tln, q.name, q.sub, q.type);
|
|
||||||
List<INSRecord> resolved = new ArrayList<>();
|
|
||||||
INSResponseStatus status = null;
|
|
||||||
|
|
||||||
if (q.sub == null && q.tln.equalsIgnoreCase("oac")) {
|
|
||||||
if (q.name.equalsIgnoreCase("info")) {
|
|
||||||
// Return INS server info site
|
|
||||||
String[] hostPort = insServer.getINSInfoSite().split(":");
|
|
||||||
resolved = List.of(new INSRecord(q.type, hostPort[0], -1, -1, Integer.parseInt(hostPort[1]), 0));
|
|
||||||
} else if (q.name.equalsIgnoreCase("register")) {
|
|
||||||
// Return INS frontend site
|
|
||||||
String[] hostPort = insServer.getINSFrontendSite().split(":");
|
|
||||||
resolved = List.of(new INSRecord(q.type, hostPort[0], -1, -1, Integer.parseInt(hostPort[1]), 0));
|
|
||||||
} else {
|
|
||||||
// Not a special name → use normal resolving
|
|
||||||
resolved = insServer.resolve(q.tln, q.name, q.sub, q.type);
|
|
||||||
}
|
|
||||||
} else if (q.sub == null && q.name.equalsIgnoreCase("info")) {
|
|
||||||
// Return TLN server info site
|
|
||||||
String resolve = insServer.resolveTLNInfoSite(q.tln);
|
|
||||||
if (resolve == null) status = INSResponseStatus.INVALID_REQUEST;
|
|
||||||
else {
|
|
||||||
String[] hostPort = resolve.split(":");
|
|
||||||
resolved = List.of(new INSRecord(q.type, hostPort[0], -1, -1, Integer.parseInt(hostPort[1]), 0));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Normal resolving
|
|
||||||
resolved = insServer.resolve(q.tln, q.name, q.sub, q.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
status = status == null && resolved.isEmpty() ? INSResponseStatus.NOT_FOUND : INSResponseStatus.OK;
|
|
||||||
INSResponsePacket response = new INSResponsePacket(status, resolved, q.clientId, insServer.getProtocolBridge());
|
|
||||||
|
|
||||||
try {
|
|
||||||
event.getConnectionHandler().sendPacket(response);
|
|
||||||
insServer.onResponseSent(q.tln, q.name, q.sub, q.type, resolved);
|
|
||||||
} catch (IOException | ClassNotFoundException e) {
|
|
||||||
insServer.onResponseSentFailed(q.tln, q.name, q.sub, q.type, resolved, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.listeners;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.ConnectionHandlerConnectedEvent;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.ConnectionHandlerDisconnectedEvent;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
|
||||||
import org.openautonomousconnection.protocol.side.web.ConnectedWebClient;
|
|
||||||
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
|
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener for web server connection events.
|
|
||||||
*/
|
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
|
||||||
public final class WebServerListener extends EventListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The reference to the ProtocolWebServer object
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private ProtocolWebServer webServer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the webServer variable
|
|
||||||
*
|
|
||||||
* @param webServer The Instance of the ProtocolWebServer
|
|
||||||
*/
|
|
||||||
public void setWebServer(ProtocolWebServer webServer) {
|
|
||||||
if (this.webServer != null) return;
|
|
||||||
this.webServer = webServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the event when a connection is established.
|
|
||||||
* Adds the connected client to the protocol web server's client list.
|
|
||||||
*
|
|
||||||
* @param event The connection handler connected event.
|
|
||||||
*/
|
|
||||||
@Listener
|
|
||||||
public void onConnect(ConnectionHandlerConnectedEvent event) {
|
|
||||||
webServer.getClients().add(new ConnectedWebClient(event.getConnectionHandler()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the event when a connection is disconnected.
|
|
||||||
* Removes the disconnected client from the protocol web server's client list.
|
|
||||||
*
|
|
||||||
* @param event The connection handler disconnected event.
|
|
||||||
*/
|
|
||||||
@Listener
|
|
||||||
public void onDisconnect(ConnectionHandlerDisconnectedEvent event) {
|
|
||||||
webServer.getClients().removeIf(client -> client.getPipelineConnection().getClientID() == -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
package org.openautonomousconnection.protocol.packets;
|
package org.openautonomousconnection.protocol.packets;
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.util.*;
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class representing a packet in the Open Autonomous Connection (OAC) protocol.
|
* Abstract class representing a packet in the Open Autonomous Connection (OAC) protocol.
|
||||||
@@ -20,8 +20,8 @@ public abstract class OACPacket extends Packet {
|
|||||||
* The protocol version associated with this packet.
|
* The protocol version associated with this packet.
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
private final ProtocolVersion protocolVersion;
|
private final List<ProtocolVersion> compatibleVersions;
|
||||||
|
private final int id;
|
||||||
/**
|
/**
|
||||||
* The response code for the packet, defaulting to RESPONSE_NOT_REQUIRED.
|
* The response code for the packet, defaulting to RESPONSE_NOT_REQUIRED.
|
||||||
*/
|
*/
|
||||||
@@ -31,11 +31,16 @@ public abstract class OACPacket extends Packet {
|
|||||||
* Constructor for OACPacket.
|
* Constructor for OACPacket.
|
||||||
*
|
*
|
||||||
* @param id The unique identifier for the packet.
|
* @param id The unique identifier for the packet.
|
||||||
* @param protocolVersion The protocol version associated with this packet.
|
* @param supportedVersions The protocol version associated with this packet.
|
||||||
*/
|
*/
|
||||||
public OACPacket(int id, ProtocolVersion protocolVersion) {
|
public OACPacket(int id, ProtocolVersion... supportedVersions) {
|
||||||
super(id);
|
this.id = id;
|
||||||
this.protocolVersion = protocolVersion;
|
this.compatibleVersions = List.of(supportedVersions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPacketID() {
|
||||||
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,61 +64,100 @@ public abstract class OACPacket extends Packet {
|
|||||||
/**
|
/**
|
||||||
* Writes the packet data to the output stream.
|
* Writes the packet data to the output stream.
|
||||||
*
|
*
|
||||||
* @param packetHandler The packet handler managing the packet.
|
* @param outputStream The output stream to write the packet data to.
|
||||||
* @param objectOutputStream The output stream to write the packet data to.
|
* @throws IOException If an I/O error occurs.
|
||||||
* @throws IOException If an I/O error occurs.
|
|
||||||
* @throws ClassNotFoundException If a class cannot be found during serialization.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final void write(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
|
public final void write(DataOutputStream outputStream) throws IOException {
|
||||||
// Write the specific packet data
|
// Write the specific packet data
|
||||||
onWrite(packetHandler, objectOutputStream);
|
onWrite(outputStream);
|
||||||
|
|
||||||
// Write the response code if the protocol version is not classic
|
// Write the response code if the protocol version is not classic
|
||||||
if (protocolVersion != ProtocolVersion.PV_1_0_0_CLASSIC) objectOutputStream.writeObject(responseCode);
|
if (!compatibleVersions.contains(ProtocolVersion.PV_1_0_0_CLASSIC)) outputStream.writeUTF(responseCode.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void read(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
|
public final void read(DataInputStream inputStream, UUID clientID) throws IOException {
|
||||||
// Read the specific packet data
|
// Read the specific packet data
|
||||||
onRead(packetHandler, objectInputStream);
|
onRead(inputStream, clientID);
|
||||||
|
|
||||||
// Read the response code if the protocol version is not classic
|
// Read the response code if the protocol version is not classic
|
||||||
if (protocolVersion != ProtocolVersion.PV_1_0_0_CLASSIC)
|
if (!compatibleVersions.contains(ProtocolVersion.PV_1_0_0_CLASSIC))
|
||||||
responseCode = (INSResponseStatus) objectInputStream.readObject();
|
responseCode = INSResponseStatus.valueOf(inputStream.readUTF());
|
||||||
else responseCode = INSResponseStatus.RESPONSE_NOT_REQUIRED;
|
else responseCode = INSResponseStatus.RESPONSE_NOT_REQUIRED;
|
||||||
|
|
||||||
// Call the response code read handler
|
// Call the response code read handler
|
||||||
onResponseCodeRead(packetHandler, objectInputStream);
|
onResponseCodeRead(inputStream, clientID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract method to be implemented by subclasses for writing specific packet data.
|
* Abstract method to be implemented by subclasses for writing specific packet data.
|
||||||
*
|
*
|
||||||
* @param packetHandler The packet handler managing the packet.
|
* @param outputStream The output stream to write the packet data to.
|
||||||
* @param objectOutputStream The output stream to write the packet data to.
|
* @throws IOException If an I/O error occurs.
|
||||||
* @throws IOException If an I/O error occurs.
|
|
||||||
* @throws ClassNotFoundException If a class cannot be found during serialization.
|
|
||||||
*/
|
*/
|
||||||
public abstract void onWrite(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException;
|
public abstract void onWrite(DataOutputStream outputStream) throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract method to be implemented by subclasses for reading specific packet data.
|
* Abstract method to be implemented by subclasses for reading specific packet data.
|
||||||
*
|
*
|
||||||
* @param packetHandler The packet handler managing the packet.
|
* @param inputStream The input stream to read the packet data from.
|
||||||
* @param objectInputStream The input stream to read the packet data from.
|
* @throws IOException If an I/O error occurs.
|
||||||
* @throws IOException If an I/O error occurs.
|
|
||||||
* @throws ClassNotFoundException If a class cannot be found during deserialization.
|
|
||||||
*/
|
*/
|
||||||
public abstract void onRead(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException;
|
public abstract void onRead(DataInputStream inputStream, UUID clientID) throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method called after the response code has been read from the input stream.
|
* 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.
|
* Subclasses can override this method to handle any additional logic based on the response code.
|
||||||
*
|
*
|
||||||
* @param packetHandler The packet handler managing the packet.
|
* @param inputStream The input stream from which the response code was read.
|
||||||
* @param objectInputStream The input stream from which the response code was read.
|
|
||||||
*/
|
*/
|
||||||
protected void onResponseCodeRead(PacketHandler packetHandler, ObjectInputStream objectInputStream) {
|
protected void onResponseCodeRead(DataInputStream inputStream, UUID clientID) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a string map in a deterministic way (no Java object serialization).
|
||||||
|
*
|
||||||
|
* @param out output stream
|
||||||
|
* @param map map to write (may be null)
|
||||||
|
* @throws IOException on I/O errors
|
||||||
|
*/
|
||||||
|
protected final void writeStringMap(DataOutputStream out, Map<String, String> map) throws IOException {
|
||||||
|
if (map == null || map.isEmpty()) {
|
||||||
|
out.writeInt(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.writeInt(map.size());
|
||||||
|
for (Map.Entry<String, String> e : map.entrySet()) {
|
||||||
|
// Null keys/values are normalized to empty strings to keep the wire format stable.
|
||||||
|
out.writeUTF((e.getKey() != null) ? e.getKey() : "");
|
||||||
|
out.writeUTF((e.getValue() != null) ? e.getValue() : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a string map in a deterministic way (no Java object serialization).
|
||||||
|
*
|
||||||
|
* @param in input stream
|
||||||
|
* @return headers map (never null)
|
||||||
|
* @throws IOException on I/O errors / invalid sizes
|
||||||
|
*/
|
||||||
|
protected final Map<String, String> readStringMap(DataInputStream in) throws IOException {
|
||||||
|
int size = in.readInt();
|
||||||
|
if (size < 0) {
|
||||||
|
throw new IOException("Negative map size");
|
||||||
|
}
|
||||||
|
if (size == 0) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> map = new LinkedHashMap<>(Math.max(16, size * 2));
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
String key = in.readUTF();
|
||||||
|
String value = in.readUTF();
|
||||||
|
map.put(key, value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,33 @@
|
|||||||
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
|
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
|
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectedClient;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler;
|
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
|
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
import org.openautonomousconnection.protocol.packets.OACPacket;
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolINSServerEvent;
|
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolINSServerEvent;
|
||||||
import org.openautonomousconnection.protocol.side.ins.ConnectedProtocolClient;
|
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolServerEvent;
|
||||||
import org.openautonomousconnection.protocol.side.ins.events.ConnectedProtocolClientEvent;
|
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
|
||||||
import org.openautonomousconnection.protocol.side.web.ConnectedWebClient;
|
import org.openautonomousconnection.protocol.side.server.events.S_CustomClientConnectedEvent;
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
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.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.io.ObjectOutputStream;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authentication packet used between client and INS/Web servers.
|
* Authentication packet used between client and INS/Web servers.
|
||||||
* <p>
|
*
|
||||||
* Responsibilities:
|
* <p>Responsibilities:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Client → Server: Sends client ID and protocol version</li>
|
* <li>Client → Server: Sends client connection id and protocol version</li>
|
||||||
* <li>Server → Client: Sends CA key, CA certificate and CA serial files</li>
|
* <li>INS Server → Client: Sends CA key, CA certificate and CA serial files</li>
|
||||||
* <li>Performs version compatibility validation</li>
|
* <li>Performs version compatibility validation</li>
|
||||||
* <li>Triggers authentication callbacks on both sides</li>
|
* <li>Triggers authentication callbacks on both sides</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
@@ -39,134 +42,182 @@ public final class AuthPacket extends OACPacket {
|
|||||||
* @param protocolBridge The protocol context of the current instance.
|
* @param protocolBridge The protocol context of the current instance.
|
||||||
*/
|
*/
|
||||||
public AuthPacket(ProtocolBridge protocolBridge) {
|
public AuthPacket(ProtocolBridge protocolBridge) {
|
||||||
this();
|
super(8, ProtocolVersion.PV_1_0_0_BETA, ProtocolVersion.PV_1_0_1_BETA);
|
||||||
this.protocolBridge = protocolBridge;
|
this.protocolBridge = protocolBridge;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Registration constructor
|
|
||||||
*/
|
|
||||||
public AuthPacket() {
|
|
||||||
super(4, ProtocolVersion.PV_1_0_0_BETA);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes authentication data to the output stream.
|
|
||||||
* <p>
|
|
||||||
* Behavior differs based on the running side:
|
|
||||||
* <ul>
|
|
||||||
* <li>INS Server → sends CA bundle to the client</li>
|
|
||||||
* <li>Client → sends client ID + protocol version</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onWrite(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
|
public void onWrite(DataOutputStream objectOutputStream) throws IOException {
|
||||||
if (protocolBridge.isRunningAsINSServer()) {
|
if (protocolBridge.isRunningAsINSServer()) {
|
||||||
objectOutputStream.writeObject(protocolBridge.getProtocolVersion());
|
objectOutputStream.writeBoolean(true);
|
||||||
|
objectOutputStream.writeUTF(protocolBridge.getProtocolValues().protocolVersion.name());
|
||||||
|
|
||||||
// Read ca files
|
|
||||||
String caKey = "N/A";
|
|
||||||
String caPem = "N/A";
|
String caPem = "N/A";
|
||||||
String caSrl = "N/A";
|
|
||||||
try {
|
|
||||||
objectOutputStream.writeUTF(protocolBridge.getProtocolINSServer().getFolderStructure().caPrefix + NetworkUtils.getPublicIPAddress());
|
|
||||||
|
|
||||||
caKey = FileUtils.readFileFull(new File(
|
try {
|
||||||
protocolBridge.getProtocolINSServer().getFolderStructure().privateCAFolder,
|
String caPrefix = protocolBridge.getProtocolServer().getFolderStructure().getCaPrefix()
|
||||||
protocolBridge.getProtocolINSServer().getFolderStructure().caPrefix + NetworkUtils.getPublicIPAddress() + ".key"));
|
+ NetworkUtils.getPublicIPAddress();
|
||||||
|
|
||||||
|
objectOutputStream.writeUTF(caPrefix);
|
||||||
|
|
||||||
caPem = FileUtils.readFileFull(new File(
|
caPem = FileUtils.readFileFull(new File(
|
||||||
protocolBridge.getProtocolINSServer().getFolderStructure().publicCAFolder,
|
protocolBridge.getProtocolServer().getFolderStructure().publicCAFolder,
|
||||||
protocolBridge.getProtocolINSServer().getFolderStructure().caPrefix + NetworkUtils.getPublicIPAddress() + ".pem"));
|
caPrefix + ".pem"));
|
||||||
|
|
||||||
caSrl = FileUtils.readFileFull(new File(
|
|
||||||
protocolBridge.getProtocolINSServer().getFolderStructure().publicCAFolder,
|
|
||||||
protocolBridge.getProtocolINSServer().getFolderStructure().caPrefix + NetworkUtils.getPublicIPAddress() + ".srl"));
|
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
protocolBridge.getLogger().exception("Failed to read ca-files", exception);
|
protocolBridge.getProtocolValues().logger.exception("Failed to read ca-files", exception);
|
||||||
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
|
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send ca data
|
|
||||||
objectOutputStream.writeUTF(caKey);
|
|
||||||
objectOutputStream.writeUTF(caPem);
|
objectOutputStream.writeUTF(caPem);
|
||||||
objectOutputStream.writeUTF(caSrl);
|
return;
|
||||||
} else if (protocolBridge.isRunningAsClient()) {
|
|
||||||
objectOutputStream.writeInt(protocolBridge.getProtocolClient().getClientINSConnection().getClientID());
|
|
||||||
objectOutputStream.writeObject(protocolBridge.getProtocolVersion());
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (protocolBridge.isRunningAsServer()) {
|
||||||
* Reads authentication data and updates protocol state.
|
objectOutputStream.writeBoolean(false);
|
||||||
* <p>
|
objectOutputStream.writeUTF(protocolBridge.getProtocolValues().protocolVersion.name());
|
||||||
* Behavior:
|
return;
|
||||||
* <ul>
|
}
|
||||||
* <li>Server validates version and registers new connected client</li>
|
|
||||||
* <li>Client saves received CA files and completes TLS initialization</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onRead(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
|
|
||||||
if (protocolBridge.isRunningAsINSServer() || protocolBridge.isRunningAsWebServer()) {
|
|
||||||
int clientID = objectInputStream.readInt();
|
|
||||||
ProtocolVersion clientVersion = (ProtocolVersion) objectInputStream.readObject();
|
|
||||||
ConnectionHandler connectionHandler = protocolBridge.getProtocolINSServer().getNetworkServer().getConnectionHandlerByID(clientID);
|
|
||||||
|
|
||||||
if (!protocolBridge.isVersionSupported(clientVersion)) {
|
if (protocolBridge.isRunningAsClient()) {
|
||||||
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
|
UUID clientConnectionId = null;
|
||||||
connectionHandler.disconnect();
|
|
||||||
return;
|
|
||||||
} else setResponseCode(INSResponseStatus.RESPONSE_AUTH_SUCCESS);
|
|
||||||
|
|
||||||
|
if (protocolBridge.getProtocolClient() != null) {
|
||||||
if (protocolBridge.isRunningAsINSServer()) {
|
if (protocolBridge.getProtocolClient().getClientINSConnection() != null && (protocolBridge.getProtocolClient().getClientServerConnection() == null)) {
|
||||||
ConnectedProtocolClient client = protocolBridge.getProtocolINSServer().getClientByID(clientID);
|
clientConnectionId = protocolBridge.getProtocolClient().getClientINSConnection().getUniqueID();
|
||||||
client.setClientVersion(clientVersion);
|
} else if (protocolBridge.getProtocolClient().getClientServerConnection() != null) {
|
||||||
protocolBridge.getProtocolSettings().eventManager.executeEvent(new ConnectedProtocolClientEvent(client));
|
clientConnectionId = protocolBridge.getProtocolClient().getClientServerConnection().getUniqueID();
|
||||||
} else {
|
} else if (protocolBridge.getProtocolClient().getClientINSConnection() != null) {
|
||||||
ConnectedWebClient client = protocolBridge.getProtocolWebServer().getClientByID(clientID);
|
clientConnectionId = protocolBridge.getProtocolClient().getClientINSConnection().getUniqueID();
|
||||||
client.setClientVersion(clientVersion);
|
|
||||||
}
|
|
||||||
} else if (protocolBridge.isRunningAsClient()) {
|
|
||||||
ProtocolVersion serverVersion = (ProtocolVersion) objectInputStream.readObject();
|
|
||||||
|
|
||||||
if (!protocolBridge.isVersionSupported(serverVersion)) {
|
|
||||||
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
|
|
||||||
protocolBridge.getProtocolClient().getClientINSConnection().disconnect();
|
|
||||||
return;
|
|
||||||
} else setResponseCode(INSResponseStatus.RESPONSE_AUTH_SUCCESS);
|
|
||||||
|
|
||||||
String caPrefix = objectInputStream.readUTF();
|
|
||||||
|
|
||||||
String caKey = objectInputStream.readUTF();
|
|
||||||
String caPem = objectInputStream.readUTF();
|
|
||||||
String caSrl = objectInputStream.readUTF();
|
|
||||||
|
|
||||||
if (caKey.equalsIgnoreCase("N/A") || caPem.equalsIgnoreCase("N/A") || caSrl.equalsIgnoreCase("N/A"))
|
|
||||||
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
|
|
||||||
else {
|
|
||||||
|
|
||||||
File caPemFile = new File(protocolBridge.getProtocolClient().getFolderStructure().publicCAFolder, caPrefix + ".pem");
|
|
||||||
File caSrlFile = new File(protocolBridge.getProtocolClient().getFolderStructure().publicCAFolder, caPrefix + ".srl");
|
|
||||||
File caKeyFile = new File(protocolBridge.getProtocolClient().getFolderStructure().privateCAFolder, caPrefix + ".key");
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!caPemFile.exists()) caPemFile.createNewFile();
|
|
||||||
if (!caSrlFile.exists()) caSrlFile.createNewFile();
|
|
||||||
if (!caKeyFile.exists()) caKeyFile.createNewFile();
|
|
||||||
|
|
||||||
FileUtils.writeFile(caPemFile, caPem);
|
|
||||||
FileUtils.writeFile(caSrlFile, caKey);
|
|
||||||
FileUtils.writeFile(caKeyFile, caSrl);
|
|
||||||
} catch (Exception exception) {
|
|
||||||
protocolBridge.getLogger().exception("Failed to create/save ca-files", exception);
|
|
||||||
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protocolBridge.getProtocolClient().setServerVersion(serverVersion);
|
objectOutputStream.writeUTF(clientConnectionId.toString());
|
||||||
protocolBridge.getProtocolSettings().eventManager.executeEvent(new ConnectedToProtocolINSServerEvent(protocolBridge.getProtocolClient()));
|
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,13 +1,14 @@
|
|||||||
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
|
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
import lombok.Getter;
|
||||||
import org.openautonomousconnection.protocol.packets.OACPacket;
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.util.UUID;
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Packet used by clients to query INS records from an INS server.
|
* Packet used by clients to query INS records from an INS server.
|
||||||
@@ -23,11 +24,16 @@ import java.io.ObjectOutputStream;
|
|||||||
*/
|
*/
|
||||||
public final class INSQueryPacket extends OACPacket {
|
public final class INSQueryPacket extends OACPacket {
|
||||||
|
|
||||||
public String tln;
|
@Getter
|
||||||
public String name;
|
private String TLN;
|
||||||
public String sub;
|
@Getter
|
||||||
public INSRecordType type;
|
private String name;
|
||||||
public int clientId;
|
@Getter
|
||||||
|
private String sub;
|
||||||
|
@Getter
|
||||||
|
private INSRecordType type;
|
||||||
|
@Getter
|
||||||
|
private UUID clientId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new INS query packet with all required parameters.
|
* Creates a new INS query packet with all required parameters.
|
||||||
@@ -38,9 +44,9 @@ public final class INSQueryPacket extends OACPacket {
|
|||||||
* @param type Record type requested.
|
* @param type Record type requested.
|
||||||
* @param clientId Sender client ID for routing.
|
* @param clientId Sender client ID for routing.
|
||||||
*/
|
*/
|
||||||
public INSQueryPacket(String tln, String name, String sub, INSRecordType type, int clientId) {
|
public INSQueryPacket(String tln, String name, String sub, INSRecordType type, UUID clientId) {
|
||||||
super(6, ProtocolVersion.PV_1_0_0_BETA);
|
this();
|
||||||
this.tln = tln;
|
this.TLN = tln;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.sub = sub;
|
this.sub = sub;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
@@ -51,36 +57,36 @@ public final class INSQueryPacket extends OACPacket {
|
|||||||
* Registration constructor
|
* Registration constructor
|
||||||
*/
|
*/
|
||||||
public INSQueryPacket() {
|
public INSQueryPacket() {
|
||||||
super(6, ProtocolVersion.PV_1_0_0_BETA);
|
super(7, ProtocolVersion.PV_1_0_0_BETA, ProtocolVersion.PV_1_0_1_BETA);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes the INS query into the stream.
|
* Serializes the INS query into the stream.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onWrite(PacketHandler handler, ObjectOutputStream out) throws IOException {
|
public void onWrite(DataOutputStream out) throws IOException {
|
||||||
out.writeUTF(tln);
|
out.writeUTF(TLN);
|
||||||
out.writeUTF(name);
|
out.writeUTF(name);
|
||||||
|
|
||||||
out.writeBoolean(sub != null);
|
out.writeBoolean(sub != null);
|
||||||
if (sub != null) out.writeUTF(sub);
|
if (sub != null) out.writeUTF(sub);
|
||||||
|
|
||||||
out.writeObject(type);
|
out.writeUTF(type.name());
|
||||||
out.writeInt(clientId);
|
out.writeUTF(clientId.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deserializes the INS query from the stream.
|
* Deserializes the INS query from the stream.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onRead(PacketHandler handler, ObjectInputStream in) throws IOException, ClassNotFoundException {
|
public void onRead(DataInputStream in, UUID clientID) throws IOException {
|
||||||
tln = in.readUTF();
|
TLN = in.readUTF();
|
||||||
name = in.readUTF();
|
name = in.readUTF();
|
||||||
|
|
||||||
boolean hasSub = in.readBoolean();
|
boolean hasSub = in.readBoolean();
|
||||||
sub = hasSub ? in.readUTF() : null;
|
sub = hasSub ? in.readUTF() : null;
|
||||||
|
|
||||||
type = (INSRecordType) in.readObject();
|
type = INSRecordType.valueOf(in.readUTF());
|
||||||
clientId = in.readInt();
|
clientId = UUID.fromString(in.readUTF());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
|
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
import lombok.Getter;
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
import org.openautonomousconnection.protocol.packets.OACPacket;
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
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.INSRecord;
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response packet returned by an INS server after resolving a query.
|
* Response packet returned by an INS server after resolving a query.
|
||||||
@@ -27,10 +28,14 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public final class INSResponsePacket extends OACPacket {
|
public final class INSResponsePacket extends OACPacket {
|
||||||
|
|
||||||
|
@Getter
|
||||||
private final ProtocolBridge bridge;
|
private final ProtocolBridge bridge;
|
||||||
public INSResponseStatus status;
|
@Getter
|
||||||
public List<INSRecord> records;
|
private INSResponseStatus status;
|
||||||
public int clientId;
|
@Getter
|
||||||
|
private List<INSRecord> records;
|
||||||
|
@Getter
|
||||||
|
private UUID clientId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a populated response packet.
|
* Creates a populated response packet.
|
||||||
@@ -40,12 +45,11 @@ public final class INSResponsePacket extends OACPacket {
|
|||||||
* @param clientId ID of requesting client.
|
* @param clientId ID of requesting client.
|
||||||
* @param bridge Protocol runtime context.
|
* @param bridge Protocol runtime context.
|
||||||
*/
|
*/
|
||||||
public INSResponsePacket(INSResponseStatus status, List<INSRecord> records, int clientId, ProtocolBridge bridge) {
|
public INSResponsePacket(INSResponseStatus status, List<INSRecord> records, UUID clientId, ProtocolBridge bridge) {
|
||||||
super(7, ProtocolVersion.PV_1_0_0_BETA);
|
this(bridge);
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.records = records;
|
this.records = records;
|
||||||
this.clientId = clientId;
|
this.clientId = clientId;
|
||||||
this.bridge = bridge;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,7 +58,7 @@ public final class INSResponsePacket extends OACPacket {
|
|||||||
* @param bridge Protocol runtime context.
|
* @param bridge Protocol runtime context.
|
||||||
*/
|
*/
|
||||||
public INSResponsePacket(ProtocolBridge bridge) {
|
public INSResponsePacket(ProtocolBridge bridge) {
|
||||||
super(7, ProtocolVersion.PV_1_0_0_BETA);
|
super(6, ProtocolVersion.PV_1_0_0_BETA, ProtocolVersion.PV_1_0_1_BETA);
|
||||||
this.bridge = bridge;
|
this.bridge = bridge;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,32 +66,32 @@ public final class INSResponsePacket extends OACPacket {
|
|||||||
* Serializes the response status, records and client ID.
|
* Serializes the response status, records and client ID.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onWrite(PacketHandler handler, ObjectOutputStream out) throws IOException {
|
public void onWrite(DataOutputStream out) throws IOException {
|
||||||
out.writeObject(status);
|
out.writeUTF(status.name());
|
||||||
out.writeInt(records.size());
|
out.writeInt(records.size());
|
||||||
|
|
||||||
for (INSRecord rec : records) {
|
for (INSRecord rec : records) {
|
||||||
out.writeObject(rec);
|
writeObject(out, rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
out.writeInt(clientId);
|
out.writeUTF(clientId.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deserializes the response, reconstructing the record list.
|
* Deserializes the response, reconstructing the record list.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onRead(PacketHandler handler, ObjectInputStream in) throws IOException, ClassNotFoundException {
|
public void onRead(DataInputStream in, UUID clientID) throws IOException {
|
||||||
status = (INSResponseStatus) in.readObject();
|
status = INSResponseStatus.valueOf(in.readUTF());
|
||||||
|
|
||||||
int size = in.readInt();
|
int size = in.readInt();
|
||||||
records = new ArrayList<>(size);
|
records = new ArrayList<>(size);
|
||||||
|
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
records.add((INSRecord) in.readObject());
|
records.add((INSRecord) readObject(in));
|
||||||
}
|
}
|
||||||
|
|
||||||
clientId = in.readInt();
|
clientId = UUID.fromString(in.readUTF());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,7 +100,7 @@ public final class INSResponsePacket extends OACPacket {
|
|||||||
* If running on a client, forwards the result to the client-side API.
|
* If running on a client, forwards the result to the client-side API.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void onResponseCodeRead(PacketHandler handler, ObjectInputStream in) {
|
protected void onResponseCodeRead(DataInputStream in, UUID clientID) {
|
||||||
if (bridge.isRunningAsClient()) {
|
if (bridge.isRunningAsClient()) {
|
||||||
bridge.getProtocolClient().onResponse(status, records);
|
bridge.getProtocolClient().onResponse(status, records);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
import org.openautonomousconnection.protocol.packets.OACPacket;
|
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal compatibility packet used when a classic-protocol packet is received
|
|
||||||
* but not supported by the current protocol version.
|
|
||||||
* <p>
|
|
||||||
* Instead of rejecting the packet entirely, the content and class name are forwarded
|
|
||||||
* to the appropriate compatibility handler:
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerINSServer}</li>
|
|
||||||
* <li>{@link org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerClient}</li>
|
|
||||||
* <li>{@link org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerWebServer}</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class UnsupportedClassicPacket extends OACPacket {
|
|
||||||
private Class<? extends OACPacket> unsupportedClassicPacket;
|
|
||||||
private Object[] content;
|
|
||||||
private ProtocolBridge protocolBridge;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a packet describing the unsupported classic packet and its content.
|
|
||||||
*
|
|
||||||
* @param unsupportedClassicPacket The packet class that was not understood.
|
|
||||||
* @param content Serialized field values.
|
|
||||||
* @param protocolBridge The protocol context.
|
|
||||||
*/
|
|
||||||
public UnsupportedClassicPacket(Class<? extends OACPacket> unsupportedClassicPacket, Object[] content, ProtocolBridge protocolBridge) {
|
|
||||||
this();
|
|
||||||
this.unsupportedClassicPacket = unsupportedClassicPacket;
|
|
||||||
this.content = content;
|
|
||||||
this.protocolBridge = protocolBridge;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registration Constructor
|
|
||||||
*/
|
|
||||||
public UnsupportedClassicPacket() {
|
|
||||||
super(5, ProtocolVersion.PV_1_0_0_BETA);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the class name and serialized object fields to the stream.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onWrite(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
|
|
||||||
if (protocolBridge.isRunningAsClient())
|
|
||||||
objectOutputStream.writeInt(protocolBridge.getProtocolClient().getClientINSConnection().getClientID());
|
|
||||||
|
|
||||||
objectOutputStream.writeUTF(unsupportedClassicPacket.getName());
|
|
||||||
objectOutputStream.writeInt(content.length);
|
|
||||||
for (Object o : content) objectOutputStream.writeObject(o);
|
|
||||||
setResponseCode(INSResponseStatus.RESPONSE_NOT_REQUIRED);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the unsupported packet data and forwards it to the appropriate compatibility handler.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onRead(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
|
|
||||||
int clientID = 0;
|
|
||||||
if (protocolBridge.isRunningAsINSServer()) clientID = objectInputStream.readInt();
|
|
||||||
String className = objectInputStream.readUTF();
|
|
||||||
int size = objectInputStream.readInt();
|
|
||||||
content = new Object[size];
|
|
||||||
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
content[i] = objectInputStream.readObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (protocolBridge.isRunningAsINSServer())
|
|
||||||
protocolBridge.getClassicHandlerINSServer().unsupportedClassicPacket(className, content, protocolBridge.getProtocolINSServer().getClientByID(clientID));
|
|
||||||
else if (protocolBridge.isRunningAsClient())
|
|
||||||
protocolBridge.getClassicHandlerClient().unsupportedClassicPacket(className, content);
|
|
||||||
else if (protocolBridge.isRunningAsWebServer())
|
|
||||||
protocolBridge.getClassicHandlerWebServer().unsupportedClassicPacket(className, content, protocolBridge.getProtocolINSServer().getClientByID(clientID));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_0.beta.web;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a web request in OAC Web protocol.
|
||||||
|
*
|
||||||
|
* <p>Important: This packet encodes headers using a deterministic UTF-based map format
|
||||||
|
* (int size, then size times: UTF key, UTF value) instead of Java object serialization.</p>
|
||||||
|
*/
|
||||||
|
public final class WebRequestPacket extends OACPacket {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private WebRequestMethod method;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private Map<String, String> headers;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private byte[] body;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an empty request packet (used by PacketHandler factory).
|
||||||
|
*/
|
||||||
|
public WebRequestPacket() {
|
||||||
|
super(10, ProtocolVersion.PV_1_0_0_BETA);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a request packet.
|
||||||
|
*
|
||||||
|
* @param path request path (e.g. "/index.html")
|
||||||
|
* @param method request method
|
||||||
|
* @param headers request headers (may be null)
|
||||||
|
* @param body request body (may be null)
|
||||||
|
*/
|
||||||
|
public WebRequestPacket(String path, WebRequestMethod method, Map<String, String> headers, byte[] body) {
|
||||||
|
this();
|
||||||
|
this.path = (path != null) ? path : "/";
|
||||||
|
this.method = (method != null) ? method : WebRequestMethod.GET;
|
||||||
|
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
|
||||||
|
this.body = (body != null) ? body : new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWrite(DataOutputStream out) throws IOException {
|
||||||
|
out.writeUTF((path != null) ? path : "/");
|
||||||
|
out.writeUTF((method != null) ? method.name() : WebRequestMethod.GET.name());
|
||||||
|
|
||||||
|
writeStringMap(out, headers);
|
||||||
|
|
||||||
|
byte[] b = (body != null) ? body : new byte[0];
|
||||||
|
out.writeInt(b.length);
|
||||||
|
out.write(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRead(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
this.path = in.readUTF();
|
||||||
|
this.method = WebRequestMethod.valueOf(in.readUTF());
|
||||||
|
|
||||||
|
this.headers = readStringMap(in);
|
||||||
|
|
||||||
|
int len = in.readInt();
|
||||||
|
if (len < 0) {
|
||||||
|
throw new IOException("Negative body length in WebRequestPacket");
|
||||||
|
}
|
||||||
|
this.body = in.readNBytes(len);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_0.beta.web;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a web response in OAC Web protocol.
|
||||||
|
*
|
||||||
|
* <p>Important: This packet encodes headers using a deterministic UTF-based map format
|
||||||
|
* (int size, then size times: UTF key, UTF value) instead of Java object serialization.</p>
|
||||||
|
*/
|
||||||
|
public final class WebResponsePacket extends OACPacket {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private int statusCode;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private String contentType;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private Map<String, String> headers;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private byte[] body;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an empty response packet (used by PacketHandler factory).
|
||||||
|
*/
|
||||||
|
public WebResponsePacket() {
|
||||||
|
super(9, ProtocolVersion.PV_1_0_0_BETA);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a response packet.
|
||||||
|
*
|
||||||
|
* @param statusCode HTTP-like status code (e.g. 200, 404, 500)
|
||||||
|
* @param contentType MIME type (e.g. "text/html")
|
||||||
|
* @param headers response headers (may be null)
|
||||||
|
* @param body response body (may be null)
|
||||||
|
*/
|
||||||
|
public WebResponsePacket(int statusCode, String contentType, Map<String, String> headers, byte[] body) {
|
||||||
|
this();
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
this.contentType = (contentType != null) ? contentType : "text/plain";
|
||||||
|
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
|
||||||
|
this.body = (body != null) ? body : new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWrite(DataOutputStream out) throws IOException {
|
||||||
|
out.writeInt(statusCode);
|
||||||
|
out.writeUTF((contentType != null) ? contentType : "text/plain");
|
||||||
|
|
||||||
|
writeStringMap(out, headers);
|
||||||
|
|
||||||
|
byte[] b = (body != null) ? body : new byte[0];
|
||||||
|
out.writeInt(b.length);
|
||||||
|
out.write(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRead(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
this.statusCode = in.readInt();
|
||||||
|
this.contentType = in.readUTF();
|
||||||
|
|
||||||
|
this.headers = readStringMap(in);
|
||||||
|
|
||||||
|
int len = in.readInt();
|
||||||
|
if (len < 0) {
|
||||||
|
throw new IOException("Negative body length in WebResponsePacket");
|
||||||
|
}
|
||||||
|
this.body = in.readNBytes(len);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public final class WebStreamChunkPacket_v1_0_0_B extends OACPacket {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private int seq;
|
||||||
|
@Getter
|
||||||
|
private byte[] data;
|
||||||
|
|
||||||
|
public WebStreamChunkPacket_v1_0_0_B() {
|
||||||
|
super(13, ProtocolVersion.PV_1_0_0_BETA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebStreamChunkPacket_v1_0_0_B(int seq, byte[] data) {
|
||||||
|
this();
|
||||||
|
this.seq = seq;
|
||||||
|
this.data = (data != null) ? data : new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWrite(DataOutputStream out) throws IOException {
|
||||||
|
out.writeInt(seq);
|
||||||
|
out.writeInt(data.length);
|
||||||
|
out.write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRead(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
seq = in.readInt();
|
||||||
|
int len = in.readInt();
|
||||||
|
if (len < 0) throw new IOException("Negative chunk length");
|
||||||
|
data = in.readNBytes(len);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public final class WebStreamEndPacket_v1_0_0_B extends OACPacket {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private boolean ok;
|
||||||
|
|
||||||
|
public WebStreamEndPacket_v1_0_0_B() {
|
||||||
|
super(13, ProtocolVersion.PV_1_0_0_BETA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebStreamEndPacket_v1_0_0_B(boolean ok) {
|
||||||
|
this();
|
||||||
|
this.ok = ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWrite(DataOutputStream out) throws IOException {
|
||||||
|
out.writeBoolean(ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRead(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
ok = in.readBoolean();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a streaming response.
|
||||||
|
*
|
||||||
|
* <p>Important: This packet encodes headers using a deterministic UTF-based map format
|
||||||
|
* (int size, then size times: UTF key, UTF value) instead of Java object serialization.</p>
|
||||||
|
*/
|
||||||
|
public final class WebStreamStartPacket_v1_0_0_B extends OACPacket {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private int statusCode;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private String contentType;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private Map<String, String> headers;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private long totalLength;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an empty start packet (used by PacketHandler factory).
|
||||||
|
*/
|
||||||
|
public WebStreamStartPacket_v1_0_0_B() {
|
||||||
|
super(11, ProtocolVersion.PV_1_0_0_BETA);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a start packet.
|
||||||
|
*
|
||||||
|
* @param statusCode status code
|
||||||
|
* @param contentType content type
|
||||||
|
* @param headers headers (may be null)
|
||||||
|
* @param totalLength total length of the stream (may be -1 if unknown)
|
||||||
|
*/
|
||||||
|
public WebStreamStartPacket_v1_0_0_B(int statusCode, String contentType, Map<String, String> headers, long totalLength) {
|
||||||
|
this();
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
this.contentType = (contentType != null) ? contentType : "application/octet-stream";
|
||||||
|
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
|
||||||
|
this.totalLength = totalLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWrite(DataOutputStream out) throws IOException {
|
||||||
|
out.writeInt(statusCode);
|
||||||
|
out.writeUTF((contentType != null) ? contentType : "application/octet-stream");
|
||||||
|
|
||||||
|
writeStringMap(out, headers);
|
||||||
|
|
||||||
|
out.writeLong(totalLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRead(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
this.statusCode = in.readInt();
|
||||||
|
this.contentType = in.readUTF();
|
||||||
|
|
||||||
|
this.headers = readStringMap(in);
|
||||||
|
|
||||||
|
this.totalLength = in.readLong();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.packets.v1_0_0.classic;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
import org.openautonomousconnection.protocol.packets.OACPacket;
|
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.UnsupportedClassicPacket;
|
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.events.Classic_DomainPacketReceivedEvent;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_RequestDomain;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
public final class Classic_DomainPacket extends OACPacket {
|
|
||||||
private Classic_RequestDomain requestDomain;
|
|
||||||
private Classic_Domain domain;
|
|
||||||
private int clientID;
|
|
||||||
private ProtocolBridge bridge;
|
|
||||||
|
|
||||||
public Classic_DomainPacket(int toClient, Classic_RequestDomain requestDomain, Classic_Domain domain, ProtocolBridge protocolBridge) {
|
|
||||||
this();
|
|
||||||
this.clientID = toClient;
|
|
||||||
this.bridge = protocolBridge;
|
|
||||||
|
|
||||||
this.requestDomain = requestDomain;
|
|
||||||
this.domain = domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Registration constructor
|
|
||||||
public Classic_DomainPacket() {
|
|
||||||
super(2, ProtocolVersion.PV_1_0_0_CLASSIC);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWrite(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
|
|
||||||
if (bridge.isRunningAsINSServer()) {
|
|
||||||
objectOutputStream.writeInt(clientID);
|
|
||||||
objectOutputStream.writeObject(requestDomain);
|
|
||||||
objectOutputStream.writeObject(domain);
|
|
||||||
} else if (bridge.isRunningAsClient()) {
|
|
||||||
clientID = bridge.getProtocolClient().getClientINSConnection().getClientID();
|
|
||||||
objectOutputStream.writeInt(clientID);
|
|
||||||
objectOutputStream.writeObject(requestDomain);
|
|
||||||
}
|
|
||||||
|
|
||||||
objectOutputStream.writeObject(Classic_ProtocolVersion.PV_1_0_0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRead(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
|
|
||||||
if (bridge.isRunningAsINSServer()) {
|
|
||||||
clientID = objectInputStream.readInt();
|
|
||||||
requestDomain = (Classic_RequestDomain) objectInputStream.readObject();
|
|
||||||
Classic_ProtocolVersion protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
|
|
||||||
|
|
||||||
try {
|
|
||||||
domain = bridge.getClassicHandlerINSServer().getDomain(requestDomain);
|
|
||||||
} catch (SQLException exception) {
|
|
||||||
exception.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
bridge.getProtocolINSServer().getNetworkServer().getEventManager().executeEvent(new Classic_DomainPacketReceivedEvent(protocolVersion, domain, requestDomain, clientID));
|
|
||||||
|
|
||||||
if (bridge.getProtocolINSServer().getClientByID(clientID).supportClientClassic())
|
|
||||||
bridge.getProtocolINSServer().getNetworkServer().getConnectionHandlerByID(clientID).sendPacket(new Classic_DomainPacket(clientID, requestDomain, domain, bridge));
|
|
||||||
else
|
|
||||||
bridge.getProtocolINSServer().getNetworkServer().getConnectionHandlerByID(clientID).sendPacket(new UnsupportedClassicPacket(Classic_PingPacket.class, new Object[]{clientID, requestDomain, domain}, bridge));
|
|
||||||
} else if (bridge.isRunningAsClient()) {
|
|
||||||
clientID = objectInputStream.readInt();
|
|
||||||
requestDomain = (Classic_RequestDomain) objectInputStream.readObject();
|
|
||||||
domain = (Classic_Domain) objectInputStream.readObject();
|
|
||||||
Classic_ProtocolVersion protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
|
|
||||||
|
|
||||||
bridge.getProtocolClient().getClientINSConnection().getEventManager().executeEvent(new Classic_DomainPacketReceivedEvent(protocolVersion, domain, requestDomain, clientID));
|
|
||||||
} else if (bridge.isRunningAsWebServer()) {
|
|
||||||
clientID = objectInputStream.readInt();
|
|
||||||
requestDomain = (Classic_RequestDomain) objectInputStream.readObject();
|
|
||||||
Classic_ProtocolVersion protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
|
|
||||||
|
|
||||||
bridge.getProtocolWebServer().getPipelineServer().getConnectionHandlerByID(clientID).sendPacket(new UnsupportedClassicPacket(Classic_PingPacket.class, new Object[]{clientID, requestDomain, domain}, bridge));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.packets.v1_0_0.classic;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
import org.openautonomousconnection.protocol.packets.OACPacket;
|
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
|
|
||||||
public final class Classic_MessagePacket extends OACPacket {
|
|
||||||
private String message;
|
|
||||||
private int clientID;
|
|
||||||
private ProtocolBridge bridge;
|
|
||||||
|
|
||||||
// Constructor with message and client id
|
|
||||||
public Classic_MessagePacket(String message, int toClient, ProtocolBridge bridge) {
|
|
||||||
this();
|
|
||||||
this.message = message;
|
|
||||||
this.clientID = toClient;
|
|
||||||
this.bridge = bridge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Classic_MessagePacket() {
|
|
||||||
super(3, ProtocolVersion.PV_1_0_0_CLASSIC);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWrite(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
|
|
||||||
if (bridge.isRunningAsINSServer() || bridge.isRunningAsWebServer())
|
|
||||||
objectOutputStream.writeInt(clientID);
|
|
||||||
else if (bridge.isRunningAsClient()) {
|
|
||||||
clientID = bridge.getProtocolClient().getClientINSConnection().getClientID();
|
|
||||||
objectOutputStream.writeInt(clientID);
|
|
||||||
}
|
|
||||||
|
|
||||||
objectOutputStream.writeUTF(message);
|
|
||||||
objectOutputStream.writeObject(Classic_ProtocolVersion.PV_1_0_0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRead(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
|
|
||||||
if (bridge.isRunningAsINSServer()) {
|
|
||||||
clientID = objectInputStream.readInt();
|
|
||||||
String message = objectInputStream.readUTF();
|
|
||||||
Classic_ProtocolVersion protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
|
|
||||||
|
|
||||||
bridge.getClassicHandlerINSServer().handleMessage(bridge.getProtocolINSServer().getClientByID(clientID), message, protocolVersion);
|
|
||||||
} else if (bridge.isRunningAsClient()) {
|
|
||||||
clientID = objectInputStream.readInt();
|
|
||||||
String message = objectInputStream.readUTF();
|
|
||||||
Classic_ProtocolVersion protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
|
|
||||||
|
|
||||||
bridge.getClassicHandlerClient().handleMessage(message, protocolVersion);
|
|
||||||
} else if (bridge.isRunningAsWebServer()) {
|
|
||||||
clientID = objectInputStream.readInt();
|
|
||||||
String message = objectInputStream.readUTF();
|
|
||||||
Classic_ProtocolVersion protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
|
|
||||||
|
|
||||||
bridge.getClassicHandlerWebServer().handleMessage(bridge.getProtocolINSServer().getClientByID(clientID), message, protocolVersion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.packets.v1_0_0.classic;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
import org.openautonomousconnection.protocol.packets.OACPacket;
|
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.UnsupportedClassicPacket;
|
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.events.Classic_PingPacketReceivedEvent;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_RequestDomain;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
public final class Classic_PingPacket extends OACPacket {
|
|
||||||
private Classic_RequestDomain requestDomain;
|
|
||||||
private Classic_Domain domain;
|
|
||||||
private int clientID;
|
|
||||||
private boolean reachable;
|
|
||||||
private Classic_ProtocolVersion protocolVersion;
|
|
||||||
private ProtocolBridge bridge;
|
|
||||||
|
|
||||||
public Classic_PingPacket(Classic_RequestDomain requestDomain, Classic_Domain domain, boolean reachable, ProtocolBridge bridge) {
|
|
||||||
this();
|
|
||||||
this.bridge = bridge;
|
|
||||||
|
|
||||||
this.requestDomain = requestDomain;
|
|
||||||
this.domain = domain;
|
|
||||||
this.reachable = reachable;
|
|
||||||
this.protocolVersion = Classic_ProtocolVersion.PV_1_0_0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Classic_PingPacket() {
|
|
||||||
super(1, ProtocolVersion.PV_1_0_0_CLASSIC);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWrite(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
|
|
||||||
if (bridge.isRunningAsINSServer()) {
|
|
||||||
objectOutputStream.writeInt(clientID);
|
|
||||||
objectOutputStream.writeObject(requestDomain);
|
|
||||||
objectOutputStream.writeObject(domain);
|
|
||||||
objectOutputStream.writeBoolean(reachable);
|
|
||||||
} else if (bridge.isRunningAsClient()) {
|
|
||||||
clientID = bridge.getProtocolClient().getClientINSConnection().getClientID();
|
|
||||||
objectOutputStream.writeInt(clientID);
|
|
||||||
objectOutputStream.writeObject(requestDomain);
|
|
||||||
}
|
|
||||||
|
|
||||||
objectOutputStream.writeObject(protocolVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRead(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
|
|
||||||
if (bridge.isRunningAsINSServer()) {
|
|
||||||
clientID = objectInputStream.readInt();
|
|
||||||
requestDomain = (Classic_RequestDomain) objectInputStream.readObject();
|
|
||||||
protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
|
|
||||||
|
|
||||||
try {
|
|
||||||
domain = bridge.getClassicHandlerINSServer().ping(requestDomain);
|
|
||||||
} catch (SQLException exception) {
|
|
||||||
exception.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
reachable = domain != null;
|
|
||||||
bridge.getProtocolINSServer().getNetworkServer().getEventManager().executeEvent(new Classic_PingPacketReceivedEvent(protocolVersion, domain, requestDomain, reachable, clientID));
|
|
||||||
if (bridge.getProtocolINSServer().getClientByID(clientID).supportClientClassic())
|
|
||||||
bridge.getProtocolINSServer().getNetworkServer().getConnectionHandlerByID(clientID).sendPacket(new Classic_PingPacket(requestDomain, domain, reachable, bridge));
|
|
||||||
else
|
|
||||||
bridge.getProtocolINSServer().getNetworkServer().getConnectionHandlerByID(clientID).sendPacket(new UnsupportedClassicPacket(Classic_PingPacket.class, new Object[]{requestDomain, domain, reachable}, bridge));
|
|
||||||
} else if (bridge.isRunningAsClient()) {
|
|
||||||
clientID = objectInputStream.readInt();
|
|
||||||
requestDomain = (Classic_RequestDomain) objectInputStream.readObject();
|
|
||||||
domain = (Classic_Domain) objectInputStream.readObject();
|
|
||||||
boolean reachable = objectInputStream.readBoolean();
|
|
||||||
Classic_ProtocolVersion protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
|
|
||||||
|
|
||||||
bridge.getClassicHandlerClient().validationCompleted(domain, reachable ? INSResponseStatus.OK : INSResponseStatus.NOT_FOUND);
|
|
||||||
bridge.getProtocolClient().getClientINSConnection().getEventManager().executeEvent(new Classic_PingPacketReceivedEvent(protocolVersion, domain, requestDomain, reachable, clientID));
|
|
||||||
} else if (bridge.isRunningAsWebServer()) {
|
|
||||||
clientID = objectInputStream.readInt();
|
|
||||||
requestDomain = (Classic_RequestDomain) objectInputStream.readObject();
|
|
||||||
protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
|
|
||||||
|
|
||||||
bridge.getProtocolWebServer().getPipelineServer().getConnectionHandlerByID(clientID).sendPacket(new UnsupportedClassicPacket(Classic_PingPacket.class, new Object[]{requestDomain}, bridge));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for all Web v1.0.1-BETA packets.
|
||||||
|
*
|
||||||
|
* <p>Ensures the {@link WebPacketHeader} is always serialized first.</p>
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public abstract class WebPacket extends OACPacket {
|
||||||
|
|
||||||
|
private WebPacketHeader header;
|
||||||
|
|
||||||
|
protected WebPacket(int packetId, ProtocolVersion version) {
|
||||||
|
super(packetId, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the header before sending.
|
||||||
|
*
|
||||||
|
* @param header header (required)
|
||||||
|
*/
|
||||||
|
public void setHeader(WebPacketHeader header) {
|
||||||
|
this.header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onWrite(DataOutputStream out) throws IOException {
|
||||||
|
if (header == null) {
|
||||||
|
throw new IOException("WebPacketHeader is required");
|
||||||
|
}
|
||||||
|
header.write(out);
|
||||||
|
writeBody(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onRead(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
this.header = WebPacketHeader.read(in);
|
||||||
|
readBody(in, clientID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes packet-specific payload after the header.
|
||||||
|
*
|
||||||
|
* @param out stream
|
||||||
|
* @throws IOException on IO error
|
||||||
|
*/
|
||||||
|
protected abstract void writeBody(DataOutputStream out) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads packet-specific payload after the header.
|
||||||
|
*
|
||||||
|
* @param in stream
|
||||||
|
* @param clientID sender client id
|
||||||
|
* @throws IOException on IO error
|
||||||
|
*/
|
||||||
|
protected abstract void readBody(DataInputStream in, UUID clientID) throws IOException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
|
||||||
|
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: replace the document content with full HTML.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public final class WebDocumentApplyRequestPacket extends WebPacket {
|
||||||
|
private String fullHtml;
|
||||||
|
private final ProtocolBridge protocolBridge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registration constructor.
|
||||||
|
*/
|
||||||
|
public WebDocumentApplyRequestPacket(ProtocolBridge protocolBridge) {
|
||||||
|
super(18, ProtocolVersion.PV_1_0_1_BETA);
|
||||||
|
this.protocolBridge = protocolBridge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebDocumentApplyRequestPacket(WebPacketHeader header, String fullHtml, ProtocolBridge protocolBridge) {
|
||||||
|
this(protocolBridge);
|
||||||
|
setHeader(header);
|
||||||
|
this.fullHtml = (fullHtml != null) ? fullHtml : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeBody(DataOutputStream out) throws IOException {
|
||||||
|
out.writeUTF((fullHtml != null) ? fullHtml : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
this.fullHtml = in.readUTF();
|
||||||
|
|
||||||
|
if (protocolBridge.isRunningAsWebServer()) {
|
||||||
|
ProtocolWebServer server = (ProtocolWebServer) protocolBridge.getProtocolServer();
|
||||||
|
CustomConnectedClient client = server.getClientByID(clientID);
|
||||||
|
client.getConnection().sendPacket(server.handleDocumentApply(client, this), TransportProtocol.TCP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: result of applying document changes.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public final class WebDocumentApplyResponsePacket extends WebPacket {
|
||||||
|
private boolean ok;
|
||||||
|
private String error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registration constructor.
|
||||||
|
*/
|
||||||
|
public WebDocumentApplyResponsePacket() {
|
||||||
|
super(5, ProtocolVersion.PV_1_0_1_BETA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebDocumentApplyResponsePacket(WebPacketHeader header, boolean ok, String error) {
|
||||||
|
this();
|
||||||
|
setHeader(header);
|
||||||
|
this.ok = ok;
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeBody(DataOutputStream out) throws IOException {
|
||||||
|
out.writeBoolean(ok);
|
||||||
|
out.writeBoolean(error != null);
|
||||||
|
if (error != null) out.writeUTF(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
this.ok = in.readBoolean();
|
||||||
|
boolean hasErr = in.readBoolean();
|
||||||
|
this.error = hasErr ? in.readUTF() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event: sends the current document snapshot (HTML) for a page.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public final class WebDocumentSnapshotEventPacket extends WebPacket {
|
||||||
|
private String baseUrl;
|
||||||
|
private String html;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registration constructor.
|
||||||
|
*/
|
||||||
|
public WebDocumentSnapshotEventPacket() {
|
||||||
|
super(17, ProtocolVersion.PV_1_0_1_BETA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebDocumentSnapshotEventPacket(WebPacketHeader header, String baseUrl, String html) {
|
||||||
|
this();
|
||||||
|
setHeader(header);
|
||||||
|
this.baseUrl = (baseUrl != null) ? baseUrl : "";
|
||||||
|
this.html = (html != null) ? html : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeBody(DataOutputStream out) throws IOException {
|
||||||
|
out.writeUTF((baseUrl != null) ? baseUrl : "");
|
||||||
|
out.writeUTF((html != null) ? html : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
this.baseUrl = in.readUTF();
|
||||||
|
this.html = in.readUTF();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acknowledges (or rejects) a navigation request.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public final class WebNavigateAckPacket extends WebPacket {
|
||||||
|
private boolean accepted;
|
||||||
|
private String reason;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registration constructor.
|
||||||
|
*/
|
||||||
|
public WebNavigateAckPacket() {
|
||||||
|
super(2, ProtocolVersion.PV_1_0_1_BETA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebNavigateAckPacket(WebPacketHeader header, boolean accepted, String reason) {
|
||||||
|
this();
|
||||||
|
setHeader(header);
|
||||||
|
this.accepted = accepted;
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeBody(DataOutputStream out) throws IOException {
|
||||||
|
out.writeBoolean(accepted);
|
||||||
|
out.writeBoolean(reason != null);
|
||||||
|
if (reason != null) out.writeUTF(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
this.accepted = in.readBoolean();
|
||||||
|
boolean hasReason = in.readBoolean();
|
||||||
|
this.reason = hasReason ? in.readUTF() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
|
||||||
|
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebCacheMode;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebTransitionType;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigation request for a tab/page.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public final class WebNavigateRequestPacket extends WebPacket {
|
||||||
|
private String url;
|
||||||
|
private String referrer;
|
||||||
|
private WebTransitionType transitionType;
|
||||||
|
private long headerProfileId;
|
||||||
|
private WebCacheMode cacheMode;
|
||||||
|
private final ProtocolBridge protocolBridge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registration constructor.
|
||||||
|
*/
|
||||||
|
public WebNavigateRequestPacket(ProtocolBridge protocolBridge) {
|
||||||
|
super(1, ProtocolVersion.PV_1_0_1_BETA);
|
||||||
|
this.protocolBridge = Objects.requireNonNull(protocolBridge, "protocolBridge");
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebNavigateRequestPacket(
|
||||||
|
WebPacketHeader header,
|
||||||
|
String url,
|
||||||
|
String referrer,
|
||||||
|
WebTransitionType transitionType,
|
||||||
|
long headerProfileId,
|
||||||
|
WebCacheMode cacheMode, ProtocolBridge protocolBridge
|
||||||
|
) {
|
||||||
|
this(protocolBridge);
|
||||||
|
setHeader(header);
|
||||||
|
this.url = (url != null) ? url : "";
|
||||||
|
this.referrer = referrer;
|
||||||
|
this.transitionType = (transitionType != null) ? transitionType : WebTransitionType.TYPED;
|
||||||
|
this.headerProfileId = headerProfileId;
|
||||||
|
this.cacheMode = (cacheMode != null) ? cacheMode : WebCacheMode.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeBody(DataOutputStream out) throws IOException {
|
||||||
|
out.writeUTF((url != null) ? url : "");
|
||||||
|
out.writeBoolean(referrer != null);
|
||||||
|
if (referrer != null) out.writeUTF(referrer);
|
||||||
|
|
||||||
|
out.writeUTF((transitionType != null) ? transitionType.name() : WebTransitionType.TYPED.name());
|
||||||
|
out.writeLong(headerProfileId);
|
||||||
|
out.writeUTF((cacheMode != null) ? cacheMode.name() : WebCacheMode.DEFAULT.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
this.url = in.readUTF();
|
||||||
|
|
||||||
|
boolean hasRef = in.readBoolean();
|
||||||
|
this.referrer = hasRef ? in.readUTF() : null;
|
||||||
|
|
||||||
|
this.transitionType = WebTransitionType.valueOf(in.readUTF());
|
||||||
|
this.headerProfileId = in.readLong();
|
||||||
|
this.cacheMode = WebCacheMode.valueOf(in.readUTF());
|
||||||
|
|
||||||
|
if (protocolBridge.isRunningAsWebServer()) {
|
||||||
|
ProtocolWebServer server = (ProtocolWebServer) protocolBridge.getProtocolServer();
|
||||||
|
CustomConnectedClient client = server.getClientByID(clientID);
|
||||||
|
client.getConnection().sendPacket(server.handleNavigate(client, this), TransportProtocol.TCP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
|
||||||
|
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebCacheMode;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebInitiatorType;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resource request
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public final class WebResourceRequestPacket extends WebPacket {
|
||||||
|
private String url;
|
||||||
|
private String method;
|
||||||
|
private Map<String, String> headers;
|
||||||
|
private byte[] body;
|
||||||
|
private String bodyContentType;
|
||||||
|
private WebInitiatorType initiatorType;
|
||||||
|
private WebCacheMode cacheMode;
|
||||||
|
private final ProtocolBridge protocolBridge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registration constructor.
|
||||||
|
*/
|
||||||
|
public WebResourceRequestPacket(ProtocolBridge protocolBridge) {
|
||||||
|
super(3, ProtocolVersion.PV_1_0_1_BETA);
|
||||||
|
this.protocolBridge = protocolBridge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebResourceRequestPacket(
|
||||||
|
WebPacketHeader header,
|
||||||
|
String url,
|
||||||
|
String method,
|
||||||
|
Map<String, String> headers,
|
||||||
|
byte[] body,
|
||||||
|
String bodyContentType,
|
||||||
|
WebInitiatorType initiatorType,
|
||||||
|
WebCacheMode cacheMode, ProtocolBridge protocolBridge
|
||||||
|
) {
|
||||||
|
this(protocolBridge);
|
||||||
|
setHeader(header);
|
||||||
|
this.url = (url != null) ? url : "";
|
||||||
|
this.method = (method != null) ? method : "GET";
|
||||||
|
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
|
||||||
|
this.body = (body != null) ? body : new byte[0];
|
||||||
|
this.bodyContentType = bodyContentType;
|
||||||
|
this.initiatorType = (initiatorType != null) ? initiatorType : WebInitiatorType.OTHER;
|
||||||
|
this.cacheMode = (cacheMode != null) ? cacheMode : WebCacheMode.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeBody(DataOutputStream out) throws IOException {
|
||||||
|
out.writeUTF((url != null) ? url : "");
|
||||||
|
out.writeUTF((method != null) ? method : "GET");
|
||||||
|
|
||||||
|
writeStringMap(out, headers);
|
||||||
|
|
||||||
|
out.writeBoolean(bodyContentType != null);
|
||||||
|
if (bodyContentType != null) out.writeUTF(bodyContentType);
|
||||||
|
|
||||||
|
out.writeUTF((initiatorType != null) ? initiatorType.name() : WebInitiatorType.OTHER.name());
|
||||||
|
out.writeUTF((cacheMode != null) ? cacheMode.name() : WebCacheMode.DEFAULT.name());
|
||||||
|
|
||||||
|
byte[] b = (body != null) ? body : new byte[0];
|
||||||
|
out.writeInt(b.length);
|
||||||
|
out.write(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
this.url = in.readUTF();
|
||||||
|
this.method = in.readUTF();
|
||||||
|
|
||||||
|
this.headers = readStringMap(in);
|
||||||
|
|
||||||
|
boolean hasBct = in.readBoolean();
|
||||||
|
this.bodyContentType = hasBct ? in.readUTF() : null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.initiatorType = WebInitiatorType.valueOf(in.readUTF());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
getProtocolBridge().getProtocolValues().logger.exception("Invalid initiator type: " + initiatorType, e);
|
||||||
|
this.initiatorType = WebInitiatorType.OTHER;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cacheMode = WebCacheMode.valueOf(in.readUTF());
|
||||||
|
|
||||||
|
int len = in.readInt();
|
||||||
|
if (len < 0) throw new IOException("Negative body length in WebResourceRequestPacket");
|
||||||
|
this.body = in.readNBytes(len);
|
||||||
|
|
||||||
|
if (protocolBridge.isRunningAsWebServer()) {
|
||||||
|
ProtocolWebServer server = (ProtocolWebServer) protocolBridge.getProtocolServer();
|
||||||
|
CustomConnectedClient client = server.getClientByID(clientID);
|
||||||
|
if (client == null) return;
|
||||||
|
client.getConnection().sendPacket(server.handleResource(client, this), TransportProtocol.TCP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resource response
|
||||||
|
*
|
||||||
|
* <p>If the response body is streamed, send this packet with an empty body and set STREAM flag in the header.</p>
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public final class WebResourceResponsePacket extends WebPacket {
|
||||||
|
|
||||||
|
private int statusCode;
|
||||||
|
private String contentType;
|
||||||
|
private Map<String, String> headers;
|
||||||
|
private byte[] body;
|
||||||
|
private String finalUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registration constructor.
|
||||||
|
*/
|
||||||
|
public WebResourceResponsePacket() {
|
||||||
|
super(4, ProtocolVersion.PV_1_0_1_BETA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebResourceResponsePacket(
|
||||||
|
WebPacketHeader header,
|
||||||
|
int statusCode,
|
||||||
|
String contentType,
|
||||||
|
Map<String, String> headers,
|
||||||
|
byte[] body,
|
||||||
|
String finalUrl
|
||||||
|
) {
|
||||||
|
this();
|
||||||
|
setHeader(header);
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
this.contentType = (contentType != null) ? contentType : "application/octet-stream";
|
||||||
|
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
|
||||||
|
this.body = (body != null) ? body : new byte[0];
|
||||||
|
this.finalUrl = finalUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeBody(DataOutputStream out) throws IOException {
|
||||||
|
out.writeInt(statusCode);
|
||||||
|
out.writeUTF((contentType != null) ? contentType : "application/octet-stream");
|
||||||
|
|
||||||
|
writeStringMap(out, headers);
|
||||||
|
|
||||||
|
out.writeBoolean(finalUrl != null);
|
||||||
|
if (finalUrl != null) out.writeUTF(finalUrl);
|
||||||
|
|
||||||
|
byte[] b = (body != null) ? body : new byte[0];
|
||||||
|
out.writeInt(b.length);
|
||||||
|
out.write(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
this.statusCode = in.readInt();
|
||||||
|
this.contentType = in.readUTF();
|
||||||
|
|
||||||
|
this.headers = readStringMap(in);
|
||||||
|
|
||||||
|
boolean hasFinal = in.readBoolean();
|
||||||
|
this.finalUrl = hasFinal ? in.readUTF() : null;
|
||||||
|
|
||||||
|
int len = in.readInt();
|
||||||
|
if (len < 0) throw new IOException("Negative body length in WebResourceResponsePacket");
|
||||||
|
this.body = in.readNBytes(len);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream body chunk.
|
||||||
|
*
|
||||||
|
* <p>Correlation is done via {@link WebPacketHeader#getRequestId()}.</p>
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public final class WebStreamChunkPacket_v1_0_1_B extends WebPacket {
|
||||||
|
private int seq;
|
||||||
|
private byte[] data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registration constructor.
|
||||||
|
*/
|
||||||
|
public WebStreamChunkPacket_v1_0_1_B() {
|
||||||
|
super(15, ProtocolVersion.PV_1_0_1_BETA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebStreamChunkPacket_v1_0_1_B(WebPacketHeader header, int seq, byte[] data) {
|
||||||
|
this();
|
||||||
|
setHeader(header);
|
||||||
|
this.seq = seq;
|
||||||
|
this.data = (data != null) ? data : new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeBody(DataOutputStream out) throws IOException {
|
||||||
|
out.writeInt(seq);
|
||||||
|
byte[] b = (data != null) ? data : new byte[0];
|
||||||
|
out.writeInt(b.length);
|
||||||
|
out.write(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
this.seq = in.readInt();
|
||||||
|
int len = in.readInt();
|
||||||
|
if (len < 0) throw new IOException("Negative chunk length in WebStreamChunkPacket");
|
||||||
|
this.data = in.readNBytes(len);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends a streamed body transfer.
|
||||||
|
*
|
||||||
|
* <p>Correlation is done via {@link WebPacketHeader#getRequestId()}.</p>
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public final class WebStreamEndPacket_v1_0_1_B extends WebPacket {
|
||||||
|
private boolean ok;
|
||||||
|
private String error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registration constructor.
|
||||||
|
*/
|
||||||
|
public WebStreamEndPacket_v1_0_1_B() {
|
||||||
|
super(16, ProtocolVersion.PV_1_0_1_BETA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebStreamEndPacket_v1_0_1_B(WebPacketHeader header, boolean ok, String error) {
|
||||||
|
this();
|
||||||
|
setHeader(header);
|
||||||
|
this.ok = ok;
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeBody(DataOutputStream out) throws IOException {
|
||||||
|
out.writeBoolean(ok);
|
||||||
|
out.writeBoolean(error != null);
|
||||||
|
if (error != null) out.writeUTF(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
this.ok = in.readBoolean();
|
||||||
|
boolean hasErr = in.readBoolean();
|
||||||
|
this.error = hasErr ? in.readUTF() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a streamed response body.
|
||||||
|
*
|
||||||
|
* <p>Correlation is done via {@link WebPacketHeader#getRequestId()}.</p>
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public final class WebStreamStartPacket_v1_0_1_B extends WebPacket {
|
||||||
|
private int statusCode;
|
||||||
|
private String contentType;
|
||||||
|
private Map<String, String> headers;
|
||||||
|
private long totalLength;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registration constructor.
|
||||||
|
*/
|
||||||
|
public WebStreamStartPacket_v1_0_1_B() {
|
||||||
|
super(14, ProtocolVersion.PV_1_0_1_BETA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebStreamStartPacket_v1_0_1_B(WebPacketHeader header, int statusCode, String contentType, Map<String, String> headers, long totalLength) {
|
||||||
|
this();
|
||||||
|
setHeader(header);
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
this.contentType = (contentType != null) ? contentType : "application/octet-stream";
|
||||||
|
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
|
||||||
|
this.totalLength = totalLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeBody(DataOutputStream out) throws IOException {
|
||||||
|
out.writeInt(statusCode);
|
||||||
|
out.writeUTF((contentType != null) ? contentType : "application/octet-stream");
|
||||||
|
writeStringMap(out, headers);
|
||||||
|
out.writeLong(totalLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
this.statusCode = in.readInt();
|
||||||
|
this.contentType = in.readUTF();
|
||||||
|
this.headers = readStringMap(in);
|
||||||
|
this.totalLength = in.readLong();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
package org.openautonomousconnection.protocol.side.client;
|
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.NetworkClient;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.ClientDisconnectedEvent;
|
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientDisconnectedEvent;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
|
import dev.unlegitdqrk.unlegitlibrary.network.utils.PemUtils;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||||
@@ -17,291 +21,341 @@ import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseSta
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.PrivateKey;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class defining the client-side protocol operations and interactions with INS and web servers.
|
* Abstract class defining the client-side protocol operations and interactions with INS and servers.
|
||||||
*/
|
*/
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.CLIENT)
|
||||||
public abstract class ProtocolClient extends DefaultMethodsOverrider {
|
public abstract class ProtocolClient extends EventListener {
|
||||||
/**
|
|
||||||
* Handles everything with INS-Connection.
|
|
||||||
*/
|
|
||||||
private final NetworkClient clientToINS;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages the folder structure for client certificates.
|
|
||||||
*/
|
|
||||||
@Getter
|
@Getter
|
||||||
private final ClientCertificateFolderStructure folderStructure;
|
private final ClientCertificateFolderStructure folderStructure;
|
||||||
/**
|
|
||||||
* The reference to the ProtocolBridge Object
|
private NetworkClient clientToINS;
|
||||||
*/
|
private NetworkClient clientToServer;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final ProtocolBridge protocolBridge;
|
private ProtocolBridge protocolBridge;
|
||||||
/**
|
|
||||||
* Stores the protocol version of the connected server.
|
private ProtocolVersion insVersion = null;
|
||||||
*/
|
|
||||||
private ProtocolVersion serverVersion = null;
|
private ProtocolVersion serverVersion = null;
|
||||||
|
|
||||||
/**
|
public ProtocolClient() {
|
||||||
* Initializes the ProtocolClient, setting up certificate folders and the INS client connection.
|
|
||||||
*
|
|
||||||
* @throws CertificateException if there are issues with the certificates.
|
|
||||||
* @throws IOException if there are I/O issues during initialization.
|
|
||||||
*/
|
|
||||||
public ProtocolClient(ProtocolBridge protocolBridge) throws CertificateException, IOException {
|
|
||||||
this.protocolBridge = protocolBridge;
|
|
||||||
|
|
||||||
// Initialize and verify certificate folders and files
|
|
||||||
folderStructure = new ClientCertificateFolderStructure();
|
folderStructure = new ClientCertificateFolderStructure();
|
||||||
|
|
||||||
// Initialize connection to INS server
|
|
||||||
clientToINS = new NetworkClient.ClientBuilder().setLogger(protocolBridge.getLogger()).setProxy(protocolBridge.getProxy()).
|
|
||||||
setHost(protocolBridge.getProtocolSettings().host).setPort(protocolBridge.getProtocolSettings().port).
|
|
||||||
setPacketHandler(protocolBridge.getProtocolSettings().packetHandler).setEventManager(protocolBridge.getProtocolSettings().eventManager).
|
|
||||||
setRootCAFolder(folderStructure.publicCAFolder).setClientCertificatesFolder(folderStructure.publicClientFolder, folderStructure.privateClientFolder).
|
|
||||||
build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public final void buildINSConnection() {
|
||||||
* Gets the INS connection client.
|
if (!protocolBridge.isRunningAsClient())
|
||||||
*
|
throw new IllegalStateException("Not running as client");
|
||||||
* @return the NetworkClient handling the INS connection.
|
|
||||||
*/
|
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() {
|
public final NetworkClient getClientINSConnection() {
|
||||||
return clientToINS;
|
return clientToINS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void checkFileExists(File folder, String prefix, String extension) throws CertificateException, IOException {
|
||||||
* Checks if the required certificate files exist in the specified folder.
|
|
||||||
*
|
|
||||||
* @param folder the folder to check for certificate files.
|
|
||||||
* @param prefix the prefix of the certificate files.
|
|
||||||
* @param extension the extension of the certificate files.
|
|
||||||
* @throws CertificateException if any required certificate file is missing or invalid.
|
|
||||||
* @throws IOException if there are I/O issues during the check.
|
|
||||||
*/
|
|
||||||
private final void checkFileExists(File folder, String prefix, String extension) throws CertificateException, IOException {
|
|
||||||
boolean found = false;
|
|
||||||
// Check if folder exists
|
|
||||||
if (folder == null) throw new FileNotFoundException("Folder does not exist");
|
if (folder == null) throw new FileNotFoundException("Folder does not exist");
|
||||||
|
|
||||||
// List files in the folder
|
|
||||||
File[] files = folder.listFiles();
|
File[] files = folder.listFiles();
|
||||||
|
|
||||||
// Check if folder is empty
|
|
||||||
if (files == null || files.length == 0)
|
if (files == null || files.length == 0)
|
||||||
throw new FileNotFoundException("Folder " + folder.getAbsolutePath() + " is empty");
|
throw new FileNotFoundException("Folder " + folder.getAbsolutePath() + " is empty");
|
||||||
|
|
||||||
// Validate each file in the folder
|
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
if (!file.getName().startsWith(prefix))
|
if (!file.getName().startsWith(prefix))
|
||||||
throw new CertificateException(file.getAbsolutePath() + " is not valid");
|
throw new CertificateException(file.getAbsolutePath() + " is not valid");
|
||||||
|
|
||||||
// Check for specific files
|
|
||||||
if (!found) found = file.getName().equalsIgnoreCase(prefix + NetworkUtils.getPublicIPAddress() + extension);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the specific file is not found, throw an exception
|
|
||||||
if (!found) throw new CertificateException("Missing " + prefix + NetworkUtils.getPublicIPAddress() + extension);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the protocol version of the connected server.
|
|
||||||
*
|
|
||||||
* @return the ProtocolVersion of the server, or PV_1_0_0_CLASSIC if not set.
|
|
||||||
*/
|
|
||||||
public final ProtocolVersion getServerVersion() {
|
public final ProtocolVersion getServerVersion() {
|
||||||
return serverVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : serverVersion;
|
return serverVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : serverVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the protocol version of the connected server.
|
|
||||||
*
|
|
||||||
* @param serverVersion the ProtocolVersion to set for the server.
|
|
||||||
*/
|
|
||||||
public final void setServerVersion(ProtocolVersion serverVersion) {
|
public final void setServerVersion(ProtocolVersion serverVersion) {
|
||||||
if (serverVersion == null) this.serverVersion = serverVersion;
|
if (this.serverVersion == null) this.serverVersion = serverVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public final ProtocolVersion getInsVersion() {
|
||||||
* Handles INS disconnection events, resetting the server version and closing the web client connection if necessary.
|
return insVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : insVersion;
|
||||||
*
|
|
||||||
* @param event the ClientDisconnectedEvent triggered on INS disconnection.
|
|
||||||
*/
|
|
||||||
public final void onINSDisconnect(ClientDisconnectedEvent event) {
|
|
||||||
// Reset server version on INS disconnect
|
|
||||||
serverVersion = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public final void setInsVersion(ProtocolVersion insVersion) {
|
||||||
* Checks if the connected server is a stable server.
|
if (this.insVersion == null) this.insVersion = insVersion;
|
||||||
*
|
|
||||||
* @return true if the server is stable, false otherwise.
|
|
||||||
*/
|
|
||||||
public final boolean isStableServer() {
|
|
||||||
// Check if the server version is stable
|
|
||||||
return !isBetaServer() && !isClassicServer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
* Checks if the connected server or its compatible versions support stable protocol.
|
public final void onDisconnect(ClientDisconnectedEvent event) {
|
||||||
*
|
if (clientToINS == null || !clientToINS.isConnected()) {
|
||||||
* @return true if stable protocol is supported, false otherwise.
|
insVersion = null;
|
||||||
*/
|
clientToINS = null;
|
||||||
public final boolean supportServerStable() {
|
disconnectFromServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientToServer == null || !clientToServer.isConnected()) {
|
||||||
|
serverVersion = null;
|
||||||
|
clientToServer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isINSStableServer() {
|
||||||
|
return !isINSBetaServer() && !isINSClassicServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportINSServerStable() {
|
||||||
boolean yes = false;
|
boolean yes = false;
|
||||||
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
|
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
|
||||||
// Check if compatible version is stable
|
|
||||||
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE;
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE;
|
||||||
if (yes) break;
|
if (yes) break;
|
||||||
}
|
}
|
||||||
|
return isINSStableServer() || yes;
|
||||||
// Check if the server version is stable
|
|
||||||
return isStableServer() || yes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public final boolean isINSBetaServer() {
|
||||||
* Checks if the connected server is a beta server.
|
return getInsVersion().getProtocolType() == ProtocolVersion.ProtocolType.BETA;
|
||||||
*
|
|
||||||
* @return true if the server is beta, false otherwise.
|
|
||||||
*/
|
|
||||||
public final boolean isBetaServer() {
|
|
||||||
// Check if the server version is beta
|
|
||||||
return getServerVersion().getProtocolType() == ProtocolVersion.ProtocolType.BETA;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public final boolean supportINSServerBeta() {
|
||||||
* Checks if the connected server or its compatible versions support beta protocol.
|
|
||||||
*
|
|
||||||
* @return true if beta protocol is supported, false otherwise.
|
|
||||||
*/
|
|
||||||
public final boolean supportServerBeta() {
|
|
||||||
boolean yes = false;
|
boolean yes = false;
|
||||||
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
|
for (ProtocolVersion compatibleVersion : getInsVersion().getCompatibleVersions()) {
|
||||||
// Check if compatible version is beta
|
|
||||||
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA;
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA;
|
||||||
if (yes) break;
|
if (yes) break;
|
||||||
}
|
}
|
||||||
|
return isINSBetaServer() || yes;
|
||||||
// Check if the server version is beta
|
|
||||||
return isBetaServer() || yes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public final boolean isINSClassicServer() {
|
||||||
* Checks if the connected server is a classic server.
|
return getInsVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
||||||
*
|
|
||||||
* @return true if the server is classic, false otherwise.
|
|
||||||
*/
|
|
||||||
public final boolean isClassicServer() {
|
|
||||||
// Check if the server version is classic
|
|
||||||
return getServerVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public final boolean supportINSServerClassic() {
|
||||||
* Checks if the connected server or its compatible versions support classic protocol.
|
|
||||||
*
|
|
||||||
* @return true if classic protocol is supported, false otherwise.
|
|
||||||
*/
|
|
||||||
public final boolean supportServerClassic() {
|
|
||||||
boolean yes = false;
|
boolean yes = false;
|
||||||
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
|
for (ProtocolVersion compatibleVersion : getInsVersion().getCompatibleVersions()) {
|
||||||
// Check if compatible version is classic
|
|
||||||
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
||||||
if (yes) break;
|
if (yes) break;
|
||||||
}
|
}
|
||||||
|
return isINSClassicServer() || yes;
|
||||||
// Check if the server version is classic
|
|
||||||
return isClassicServer() || yes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public final boolean supportINSServerPacket(OACPacket packet) {
|
||||||
* Checks if the connected server supports the protocol version of the given packet.
|
boolean compatible = false;
|
||||||
*
|
|
||||||
* @param packet the OACPacket to check against the server's supported protocol version.
|
for (ProtocolVersion compatibleVersion : packet.getCompatibleVersions()) {
|
||||||
* @return true if the server supports the packet's protocol version, false otherwise.
|
if (!compatible) compatible = supportINSServerVersion(compatibleVersion);
|
||||||
*/
|
}
|
||||||
public final boolean supportServerPacket(OACPacket packet) {
|
|
||||||
// Check if the server supports the protocol version of the packet
|
return compatible;
|
||||||
return supportServerVersion(packet.getProtocolVersion());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public final boolean supportINSServerVersion(ProtocolVersion targetVersion) {
|
||||||
* Checks if the connected server or its compatible versions support the specified protocol version.
|
return getInsVersion() == targetVersion || getInsVersion().getCompatibleVersions().contains(targetVersion);
|
||||||
*
|
|
||||||
* @param targetVersion the ProtocolVersion to check for support.
|
|
||||||
* @return true if the server or its compatible versions support the target version, false otherwise.
|
|
||||||
*/
|
|
||||||
public final boolean supportServerVersion(ProtocolVersion targetVersion) {
|
|
||||||
// Directly check if the server version matches or is in the list of compatible versions
|
|
||||||
return getServerVersion() == targetVersion || getServerVersion().getCompatibleVersions().contains(targetVersion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public final boolean supportINSServerProtocol(ProtocolVersion.Protocol protocol) {
|
||||||
* Checks if the connected server or its compatible versions support the specified protocol.
|
|
||||||
*
|
|
||||||
* @param protocol the Protocol to check for support.
|
|
||||||
* @return true if the server or its compatible versions support the protocol, false otherwise.
|
|
||||||
*/
|
|
||||||
public final boolean supportServerProtocol(ProtocolVersion.Protocol protocol) {
|
|
||||||
boolean yes = false;
|
boolean yes = false;
|
||||||
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
|
for (ProtocolVersion compatibleVersion : getInsVersion().getCompatibleVersions()) {
|
||||||
// Check if compatible version supports the protocol
|
|
||||||
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
|
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
|
||||||
if (yes) break;
|
if (yes) break;
|
||||||
}
|
}
|
||||||
|
return getInsVersion().getSupportedProtocols().contains(protocol) || yes;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the server version supports the protocol
|
public final boolean isStableServer() {
|
||||||
|
return !isBetaServer() && !isClassicServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportServerStable() {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
|
||||||
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE;
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
return isStableServer() || yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isBetaServer() {
|
||||||
|
return getServerVersion().getProtocolType() == ProtocolVersion.ProtocolType.BETA;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportServerBeta() {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
|
||||||
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA;
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
return isBetaServer() || yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isClassicServer() {
|
||||||
|
return getServerVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportServerClassic() {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
|
||||||
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
return isClassicServer() || yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportServerPacket(OACPacket packet) {
|
||||||
|
boolean compatible = false;
|
||||||
|
|
||||||
|
for (ProtocolVersion compatibleVersion : packet.getCompatibleVersions()) {
|
||||||
|
if (!compatible) compatible = supportServerVersion(compatibleVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
return compatible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportServerVersion(ProtocolVersion targetVersion) {
|
||||||
|
return getServerVersion() == targetVersion || getServerVersion().getCompatibleVersions().contains(targetVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportServerProtocol(ProtocolVersion.Protocol protocol) {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
|
||||||
|
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
return getServerVersion().getSupportedProtocols().contains(protocol) || yes;
|
return getServerVersion().getSupportedProtocols().contains(protocol) || yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public final void sendINSQuery(String tln, String name, String sub, INSRecordType type) throws Exception {
|
||||||
* Sends an INS query to the INS server.
|
|
||||||
* <p>
|
|
||||||
* The client requests a specific record type for a given TLN, name and optional subname.
|
|
||||||
* After sending the packet, {@link #onQuerySent(String, String, String, INSRecordType)} is invoked.
|
|
||||||
*
|
|
||||||
* @param tln The TLN.
|
|
||||||
* @param name The InfoName.
|
|
||||||
* @param sub Optional subname or {@code null}.
|
|
||||||
* @param type The requested record type.
|
|
||||||
* @throws IOException If sending the packet fails.
|
|
||||||
* @throws ClassNotFoundException If packet serialization fails.
|
|
||||||
*/
|
|
||||||
public final void sendINSQuery(String tln, String name, String sub, INSRecordType type) throws IOException, ClassNotFoundException {
|
|
||||||
if (!protocolBridge.isRunningAsClient()) return;
|
if (!protocolBridge.isRunningAsClient()) return;
|
||||||
|
|
||||||
getClientINSConnection().sendPacket(new INSQueryPacket(tln, name, sub, type, getClientINSConnection().getClientID()));
|
getClientINSConnection().sendPacket(
|
||||||
|
new INSQueryPacket(tln, name, sub, type, getClientINSConnection().getUniqueID()),
|
||||||
|
TransportProtocol.TCP
|
||||||
|
);
|
||||||
onQuerySent(tln, name, sub, type);
|
onQuerySent(tln, name, sub, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private final void disconnectFromServer() {
|
||||||
* Called when the client receives an INS response from the server.
|
if (clientToServer != null) {
|
||||||
* <p>
|
if (clientToINS == null || !clientToINS.isConnected())
|
||||||
*
|
protocolBridge.getProtocolValues().eventManager.unregisterListener(this);
|
||||||
* @param status The response status from the server.
|
clientToServer.disconnect();
|
||||||
* @param records The list of records returned by the server.
|
clientToServer = null;
|
||||||
*/
|
}
|
||||||
public abstract void onResponse(INSResponseStatus status, List<INSRecord> records);
|
}
|
||||||
|
|
||||||
|
public void onResponse(INSResponseStatus status, List<INSRecord> records) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called after a query was sent to the INS server.
|
|
||||||
* <p>
|
|
||||||
*
|
|
||||||
* @param tln The TLN requested.
|
|
||||||
* @param name The InfoName.
|
|
||||||
* @param sub Optional subname.
|
|
||||||
* @param type The requested record type.
|
|
||||||
*/
|
|
||||||
public void onQuerySent(String tln, String name, String sub, INSRecordType type) {
|
public void onQuerySent(String tln, String name, String sub, INSRecordType type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the folder structure for client certificates.
|
* Called when no stored INS fingerprint exists yet (trust-on-first-use).
|
||||||
|
*
|
||||||
|
* @param caFingerprint received fingerprint of the INS CA certificate
|
||||||
|
* @return {@code true} to allow the connection; {@code false} to reject it
|
||||||
*/
|
*/
|
||||||
public final class ClientCertificateFolderStructure {
|
public abstract boolean trustINS(String caFingerprint);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a stored INS fingerprint does not match the received one.
|
||||||
|
*
|
||||||
|
* @param oldCAFingerprint previously stored fingerprint
|
||||||
|
* @param newCAFingerprint received fingerprint of the INS CA certificate
|
||||||
|
* @return {@code true} to accept the new fingerprint; {@code false} to reject it
|
||||||
|
*/
|
||||||
|
public abstract boolean trustNewINSFingerprint(String oldCAFingerprint, String newCAFingerprint);
|
||||||
|
|
||||||
|
public static final class ClientCertificateFolderStructure {
|
||||||
public final File certificatesFolder;
|
public final File certificatesFolder;
|
||||||
|
|
||||||
public final File publicFolder;
|
public final File publicFolder;
|
||||||
@@ -338,3 +392,4 @@ public abstract class ProtocolClient extends DefaultMethodsOverrider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,407 @@
|
|||||||
|
package org.openautonomousconnection.protocol.side.client;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamChunkPacket_v1_0_0_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamEndPacket_v1_0_0_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamStartPacket_v1_0_0_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentSnapshotEventPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateAckPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamChunkPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamEndPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamStartPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.*;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebCompatMapper;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web-capable protocol client built on top of {@link ProtocolClient}.
|
||||||
|
*
|
||||||
|
* <p>Compatibility strategy:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>In Web v1.0.0 mode, correlation is only possible if there is exactly ONE in-flight request.</li>
|
||||||
|
* <li>This client therefore enforces single in-flight request and maps responses/streams
|
||||||
|
* onto that single active {@link WebPacketHeader}.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public abstract class ProtocolWebClient extends ProtocolClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web header factory bound to this client instance.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final WebHeaderFactory webHeaderFactory = new WebHeaderFactory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tab contexts by tabId (stable).
|
||||||
|
*/
|
||||||
|
private final Map<Long, WebHeaderFactory.WebTabContext> tabs = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single in-flight correlation header.
|
||||||
|
*
|
||||||
|
* <p>v1.0.0 has no correlation fields; without changing the v1.0.0 server this is the only deterministic mapping.</p>
|
||||||
|
*/
|
||||||
|
private final AtomicReference<WebPacketHeader> v100BInFlight = new AtomicReference<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True once we observed a 1.0.0 stream start for the current in-flight request.
|
||||||
|
*/
|
||||||
|
private volatile boolean v100BStreaming;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and registers a new tab context.
|
||||||
|
*/
|
||||||
|
public final WebHeaderFactory.WebTabContext createTab() {
|
||||||
|
WebHeaderFactory.WebTabContext tab = webHeaderFactory.createTab();
|
||||||
|
tabs.put(tab.getTabId(), tab);
|
||||||
|
return tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a tab context.
|
||||||
|
*/
|
||||||
|
public final void closeTab(long tabId) {
|
||||||
|
tabs.remove(tabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the tab context for a tab id.
|
||||||
|
*/
|
||||||
|
public final WebHeaderFactory.WebTabContext getTab(long tabId) {
|
||||||
|
return tabs.get(tabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins a new navigation for the given tab.
|
||||||
|
*/
|
||||||
|
public final long beginNavigation(WebHeaderFactory.WebTabContext tab) {
|
||||||
|
Objects.requireNonNull(tab, "tab");
|
||||||
|
return webHeaderFactory.beginNavigation(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void sendNavigate(
|
||||||
|
WebHeaderFactory.WebTabContext tab,
|
||||||
|
String url,
|
||||||
|
String referrer,
|
||||||
|
WebTransitionType transition,
|
||||||
|
long headerProfileId,
|
||||||
|
WebCacheMode cacheMode,
|
||||||
|
boolean noStore
|
||||||
|
) throws Exception {
|
||||||
|
Objects.requireNonNull(tab, "tab");
|
||||||
|
ensureServerConnected();
|
||||||
|
|
||||||
|
WebPacketHeader header = webHeaderFactory.navigation(tab, noStore);
|
||||||
|
header = new WebPacketHeader(
|
||||||
|
header.getRequestId(),
|
||||||
|
header.getTabId(),
|
||||||
|
header.getPageId(),
|
||||||
|
header.getFrameId(),
|
||||||
|
header.getFlags() | WebPacketFlags.NAVIGATION,
|
||||||
|
header.getTimestampMs()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (is100BServer()) {
|
||||||
|
begin100BRequest(header);
|
||||||
|
WebRequestPacket packet = new WebRequestPacket(
|
||||||
|
url,
|
||||||
|
WebRequestMethod.GET,
|
||||||
|
Collections.emptyMap(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
getClientServerConnection().sendPacket(packet, TransportProtocol.TCP);
|
||||||
|
onWebRequestSent(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WebNavigateRequestPacket pkt = new WebNavigateRequestPacket(
|
||||||
|
header,
|
||||||
|
url,
|
||||||
|
referrer,
|
||||||
|
transition,
|
||||||
|
headerProfileId,
|
||||||
|
cacheMode, getProtocolBridge()
|
||||||
|
);
|
||||||
|
|
||||||
|
getClientServerConnection().sendPacket(pkt, TransportProtocol.TCP);
|
||||||
|
onWebRequestSent(pkt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final long sendResource(
|
||||||
|
WebHeaderFactory.WebTabContext tab,
|
||||||
|
long frameId,
|
||||||
|
String url,
|
||||||
|
String method,
|
||||||
|
Map<String, String> headers,
|
||||||
|
byte[] body,
|
||||||
|
String bodyContentType,
|
||||||
|
WebInitiatorType initiator,
|
||||||
|
WebCacheMode cacheMode,
|
||||||
|
boolean noStore
|
||||||
|
) throws Exception {
|
||||||
|
Objects.requireNonNull(tab, "tab");
|
||||||
|
ensureServerConnected();
|
||||||
|
|
||||||
|
WebPacketHeader base = webHeaderFactory.resource(tab, frameId, noStore);
|
||||||
|
WebPacketHeader header = new WebPacketHeader(
|
||||||
|
base.getRequestId(),
|
||||||
|
base.getTabId(),
|
||||||
|
base.getPageId(),
|
||||||
|
base.getFrameId(),
|
||||||
|
base.getFlags() | WebPacketFlags.RESOURCE,
|
||||||
|
base.getTimestampMs()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (is100BServer()) {
|
||||||
|
begin100BRequest(header);
|
||||||
|
|
||||||
|
WebRequestPacket packet = new WebRequestPacket(
|
||||||
|
url,
|
||||||
|
WebCompatMapper.map100BMethod(method),
|
||||||
|
headers != null ? headers : Collections.emptyMap(),
|
||||||
|
body
|
||||||
|
);
|
||||||
|
|
||||||
|
getClientServerConnection().sendPacket(packet, TransportProtocol.TCP);
|
||||||
|
onWebRequestSent(null);
|
||||||
|
return header.getRequestId();
|
||||||
|
}
|
||||||
|
|
||||||
|
WebResourceRequestPacket pkt = new WebResourceRequestPacket(
|
||||||
|
header,
|
||||||
|
url,
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
bodyContentType,
|
||||||
|
initiator,
|
||||||
|
cacheMode, getProtocolBridge()
|
||||||
|
);
|
||||||
|
|
||||||
|
getClientServerConnection().sendPacket(pkt, TransportProtocol.TCP);
|
||||||
|
onWebRequestSent(pkt);
|
||||||
|
return header.getRequestId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final long sendDocumentApply(WebHeaderFactory.WebTabContext tab, long frameId, String fullHtml) throws Exception {
|
||||||
|
Objects.requireNonNull(tab, "tab");
|
||||||
|
ensureServerConnected();
|
||||||
|
|
||||||
|
if (is100BServer()) {
|
||||||
|
throw new UnsupportedOperationException("Document apply is not supported by Web v1.0.0 servers.");
|
||||||
|
}
|
||||||
|
|
||||||
|
WebPacketHeader base = webHeaderFactory.documentApply(tab, frameId);
|
||||||
|
WebPacketHeader header = new WebPacketHeader(
|
||||||
|
base.getRequestId(),
|
||||||
|
base.getTabId(),
|
||||||
|
base.getPageId(),
|
||||||
|
base.getFrameId(),
|
||||||
|
base.getFlags(),
|
||||||
|
base.getTimestampMs()
|
||||||
|
);
|
||||||
|
|
||||||
|
WebDocumentApplyRequestPacket pkt = new WebDocumentApplyRequestPacket(header, fullHtml, getProtocolBridge());
|
||||||
|
getClientServerConnection().sendPacket(pkt, TransportProtocol.TCP);
|
||||||
|
onWebRequestSent(pkt);
|
||||||
|
return pkt.getHeader().getRequestId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stale detection: drops packets that refer to an old pageId for the same tab.
|
||||||
|
*/
|
||||||
|
public final boolean isStale(WebPacketHeader header) {
|
||||||
|
if (header == null) return false;
|
||||||
|
WebHeaderFactory.WebTabContext tab = tabs.get(header.getTabId());
|
||||||
|
if (tab == null) return false;
|
||||||
|
return webHeaderFactory.isStale(tab, header);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook invoked after sending a web request packet (may be null in 1.0.0 mode).
|
||||||
|
*/
|
||||||
|
protected void onWebRequestSent(WebPacket packet) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onNavigateAck(WebNavigateAckPacket packet) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onResourceResponse(WebResourceResponsePacket packet) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onStreamStart(WebStreamStartPacket_v1_0_1_B packet) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onStreamChunk(WebStreamChunkPacket_v1_0_1_B packet) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onStreamEnd(WebStreamEndPacket_v1_0_1_B packet) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onDocumentSnapshot(WebDocumentSnapshotEventPacket packet) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onDocumentApplyResponse(WebDocumentApplyResponsePacket packet) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
|
public final void onPacket(C_PacketReadEvent event) {
|
||||||
|
Packet p = event.getPacket();
|
||||||
|
|
||||||
|
// Native v1.0.1 packets
|
||||||
|
if (p instanceof WebPacket packet) {
|
||||||
|
handleIncoming(packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is100BServer()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1.0.0 mapping requires an active correlation
|
||||||
|
WebPacketHeader corr = v100BInFlight.get();
|
||||||
|
if (corr == null) {
|
||||||
|
// Deterministic mapping is impossible without correlation.
|
||||||
|
// Dropping is safer than misrouting.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1.0.0 response -> v1.0.1 resource response
|
||||||
|
if (p instanceof WebResponsePacket resp) {
|
||||||
|
WebResourceResponsePacket mapped = WebCompatMapper.toV101ResourceResponse(corr, resp);
|
||||||
|
onResourceResponse(mapped);
|
||||||
|
|
||||||
|
// If this response is not part of a stream, release correlation.
|
||||||
|
if (!looksLike100BStreamHandshake(resp) && !v100BStreaming) {
|
||||||
|
end100BRequest();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1.0.0 stream start/chunk/end -> v1.0.1 stream packets
|
||||||
|
if (p instanceof WebStreamStartPacket_v1_0_0_B start) {
|
||||||
|
v100BStreaming = true;
|
||||||
|
WebStreamStartPacket_v1_0_1_B mapped = WebCompatMapper.toV101StreamStart(corr, start);
|
||||||
|
onStreamStart(mapped);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p instanceof WebStreamChunkPacket_v1_0_0_B chunk) {
|
||||||
|
WebStreamChunkPacket_v1_0_1_B mapped = WebCompatMapper.toV101StreamChunk(corr, chunk);
|
||||||
|
onStreamChunk(mapped);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p instanceof WebStreamEndPacket_v1_0_0_B end) {
|
||||||
|
WebStreamEndPacket_v1_0_1_B mapped = WebCompatMapper.toV101StreamEnd(corr, end);
|
||||||
|
onStreamEnd(mapped);
|
||||||
|
|
||||||
|
// stream finished -> release correlation
|
||||||
|
end100BRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Central dispatcher for incoming v1.0.1 web packets.
|
||||||
|
*/
|
||||||
|
public final void handleIncoming(WebPacket packet) {
|
||||||
|
if (packet == null) return;
|
||||||
|
|
||||||
|
WebPacketHeader h = packet.getHeader();
|
||||||
|
if (isStale(h)) return;
|
||||||
|
|
||||||
|
switch (packet) {
|
||||||
|
case WebNavigateAckPacket p -> onNavigateAck(p);
|
||||||
|
case WebResourceResponsePacket p -> onResourceResponse(p);
|
||||||
|
case WebStreamStartPacket_v1_0_1_B p -> onStreamStart(p);
|
||||||
|
case WebStreamChunkPacket_v1_0_1_B p -> onStreamChunk(p);
|
||||||
|
case WebStreamEndPacket_v1_0_1_B p -> onStreamEnd(p);
|
||||||
|
case WebDocumentSnapshotEventPacket p -> onDocumentSnapshot(p);
|
||||||
|
case WebDocumentApplyResponsePacket p -> onDocumentApplyResponse(p);
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureServerConnected() {
|
||||||
|
if (getClientServerConnection() == null) throw new IllegalStateException("Server connection is not built");
|
||||||
|
if (!getClientServerConnection().isConnected() && !getClientServerConnection().isTCPConnected()) {
|
||||||
|
throw new IllegalStateException("Server connection is not connected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean is100BServer() {
|
||||||
|
return getServerVersion().equals(ProtocolVersion.PV_1_0_0_BETA);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins a deterministic 1.0.0 request mapping.
|
||||||
|
*
|
||||||
|
* <p>Enforces single in-flight 1.0.0 request.</p>
|
||||||
|
*
|
||||||
|
* @param correlation correlation header to use for mapping 1.0.0 responses
|
||||||
|
*/
|
||||||
|
private void begin100BRequest(WebPacketHeader correlation) {
|
||||||
|
Objects.requireNonNull(correlation, "correlation");
|
||||||
|
v100BStreaming = false;
|
||||||
|
|
||||||
|
if (!v100BInFlight.compareAndSet(null, correlation)) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Web v1.0.0 mode supports only 1 in-flight request. " +
|
||||||
|
"Wait for response/stream end before sending the next request."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases 1.0.0 correlation state.
|
||||||
|
*/
|
||||||
|
private void end100BRequest() {
|
||||||
|
v100BStreaming = false;
|
||||||
|
v100BInFlight.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heuristic: checks if a 1.0.0 response indicates streaming will follow.
|
||||||
|
*
|
||||||
|
* <p>Your v1.0.0 WebServer uses: header 'x-oac-stream' = '1' for the 202 response before streaming.</p>
|
||||||
|
*
|
||||||
|
* @param resp 1.0.0 response
|
||||||
|
* @return true if likely stream handshake
|
||||||
|
*/
|
||||||
|
private boolean looksLike100BStreamHandshake(WebResponsePacket resp) {
|
||||||
|
if (resp == null) return false;
|
||||||
|
Map<String, String> h = resp.getHeaders();
|
||||||
|
if (h == null) return false;
|
||||||
|
|
||||||
|
// Case-insensitive lookup without allocating
|
||||||
|
for (Map.Entry<String, String> e : h.entrySet()) {
|
||||||
|
if (e.getKey() != null && e.getValue() != null
|
||||||
|
&& e.getKey().equalsIgnoreCase("x-oac-stream")
|
||||||
|
&& e.getValue().equals("1")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package org.openautonomousconnection.protocol.side.client.events;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||||
|
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event triggered when a client successfully connects to a INS protocol server.
|
||||||
|
*/
|
||||||
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
|
||||||
|
public final class ConnectedToProtocolServerEvent extends Event {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the ProtocolClient object.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final ProtocolClient client;
|
||||||
|
|
||||||
|
public ConnectedToProtocolServerEvent(ProtocolClient client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,164 +1,71 @@
|
|||||||
package org.openautonomousconnection.protocol.side.ins;
|
package org.openautonomousconnection.protocol.side.ins;
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.file.ConfigurationManager;
|
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
|
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.ProtocolCustomServer;
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
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.INSRecord;
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class representing a INS server in the protocol.
|
* Abstract class representing a INS server in the protocol.
|
||||||
*/
|
*/
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
|
||||||
public abstract class ProtocolINSServer extends DefaultMethodsOverrider {
|
public abstract class ProtocolINSServer extends ProtocolCustomServer {
|
||||||
/**
|
|
||||||
* The network server instance.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private final NetworkServer networkServer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The configuration manager for handling server configurations.
|
|
||||||
*/
|
|
||||||
private final ConfigurationManager configurationManager;
|
|
||||||
/**
|
|
||||||
* The reference to the ProtocolBridge Object
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private final ProtocolBridge protocolBridge;
|
|
||||||
/**
|
|
||||||
* List of connected protocol clients.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private List<ConnectedProtocolClient> clients;
|
|
||||||
/**
|
|
||||||
* The folder structure for server certificates.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private ServerCertificateFolderStructure folderStructure;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a ProtocolINSServer with the specified configuration file.
|
|
||||||
*
|
|
||||||
* @param configFile The configuration file for the INS server.
|
|
||||||
* @throws IOException If an I/O error occurs.
|
|
||||||
* @throws CertificateException If a certificate error occurs.
|
|
||||||
*/
|
|
||||||
public ProtocolINSServer(File configFile, ProtocolBridge protocolBridge) throws IOException, CertificateException {
|
|
||||||
this.protocolBridge = protocolBridge;
|
|
||||||
// Ensure the configuration file exists
|
|
||||||
if (!configFile.exists()) configFile.createNewFile();
|
|
||||||
|
|
||||||
// Load the configuration properties
|
|
||||||
configurationManager = new ConfigurationManager(configFile);
|
|
||||||
configurationManager.loadProperties();
|
|
||||||
|
|
||||||
// Set default values for configuration properties if not already set
|
|
||||||
if (!configurationManager.isSet("server.site.info")) {
|
|
||||||
configurationManager.set("server.site.info", "INS-SERVER INFO SITE IP:PORT");
|
|
||||||
configurationManager.saveProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!configurationManager.isSet("server.site.frontend")) {
|
|
||||||
configurationManager.set("server.site.frontend", "SERVER IP TO INS-FRONTEND:PORT");
|
|
||||||
configurationManager.saveProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the folder structure
|
|
||||||
folderStructure = new ServerCertificateFolderStructure();
|
|
||||||
|
|
||||||
// Check for the existence of necessary certificate files
|
|
||||||
checkFileExists(folderStructure.publicCAFolder, folderStructure.caPrefix, ".pem");
|
|
||||||
checkFileExists(folderStructure.publicCAFolder, folderStructure.caPrefix, ".srl");
|
|
||||||
checkFileExists(folderStructure.privateCAFolder, folderStructure.caPrefix, ".key");
|
|
||||||
|
|
||||||
checkFileExists(folderStructure.publicServerFolder, folderStructure.certPrefix, ".crt");
|
|
||||||
checkFileExists(folderStructure.privateServerFolder, folderStructure.certPrefix, ".key");
|
|
||||||
|
|
||||||
// Define the certificate and key files based on the public IP address
|
|
||||||
File certFile = new File(folderStructure.publicServerFolder, folderStructure.certPrefix + NetworkUtils.getPublicIPAddress() + ".crt");
|
|
||||||
File keyFile = new File(folderStructure.privateServerFolder, folderStructure.certPrefix + NetworkUtils.getPublicIPAddress() + ".key");
|
|
||||||
|
|
||||||
// Initialize the protocol bridge and clients list
|
|
||||||
this.clients = new ArrayList<>();
|
|
||||||
|
|
||||||
// Build the network server with the specified settings
|
|
||||||
this.networkServer = new NetworkServer.ServerBuilder().
|
|
||||||
setLogger(protocolBridge.getLogger()).
|
|
||||||
setEventManager(protocolBridge.getProtocolSettings().eventManager).
|
|
||||||
setPacketHandler(protocolBridge.getProtocolSettings().packetHandler).
|
|
||||||
setPort(protocolBridge.getProtocolSettings().port).
|
|
||||||
setRequireClientCertificate(false).setRootCAFolder(folderStructure.publicCAFolder).setServerCertificate(certFile, keyFile).
|
|
||||||
build();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the required certificate files exist in the specified folder.
|
|
||||||
*
|
|
||||||
* @param folder The folder to check for certificate files.
|
|
||||||
* @param prefix The prefix of the certificate files.
|
|
||||||
* @param extension The extension of the certificate files.
|
|
||||||
* @throws CertificateException If a certificate error occurs.
|
|
||||||
* @throws IOException If an I/O error occurs.
|
|
||||||
*/
|
|
||||||
private final void checkFileExists(File folder, String prefix, String extension) throws CertificateException, IOException {
|
|
||||||
boolean found = false;
|
|
||||||
|
|
||||||
// Check if the folder exists
|
|
||||||
if (folder == null) throw new FileNotFoundException("Folder does not exist");
|
|
||||||
|
|
||||||
// List all files in the folder
|
|
||||||
File[] files = folder.listFiles();
|
|
||||||
|
|
||||||
// Check if the folder is empty
|
|
||||||
if (files == null || files.length == 0)
|
|
||||||
throw new FileNotFoundException("Folder " + folder.getAbsolutePath() + " is empty");
|
|
||||||
|
|
||||||
// Validate each file in the folder
|
|
||||||
for (File file : files) {
|
|
||||||
if (!file.getName().startsWith(prefix))
|
|
||||||
throw new CertificateException(file.getAbsolutePath() + " is not valid");
|
|
||||||
|
|
||||||
// Check if the file matches the expected naming convention
|
|
||||||
if (!found) found = file.getName().equalsIgnoreCase(prefix + NetworkUtils.getPublicIPAddress() + extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the required file is not found, throw an exception
|
|
||||||
if (!found) throw new CertificateException("Missing " + prefix + NetworkUtils.getPublicIPAddress() + extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a connected protocol client by its client ID.
|
|
||||||
*
|
|
||||||
* @param clientID The ID of the client to retrieve.
|
|
||||||
* @return The ConnectedProtocolClient with the specified ID, or null if not found.
|
|
||||||
*/
|
|
||||||
public final ConnectedProtocolClient getClientByID(int clientID) {
|
|
||||||
for (ConnectedProtocolClient client : clients)
|
|
||||||
if (client.getConnectionHandler().getClientID() == clientID) return client;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the INS information site URL from the configuration.
|
* Gets the INS information site URL from the configuration.
|
||||||
*
|
*
|
||||||
* @return The INS information site URL.
|
* @return The INS information site URL.
|
||||||
*/
|
*/
|
||||||
public final String getINSInfoSite() {
|
private final String insInfoSite;
|
||||||
return configurationManager.getString("server.site.info");
|
/**
|
||||||
|
* Gets the INS registration site URL from the configuration.
|
||||||
|
*
|
||||||
|
* @return The INS registration site URL.
|
||||||
|
*/
|
||||||
|
private final String insFrontendSite;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a ProtocolINSServer with the specified configuration file.
|
||||||
|
*
|
||||||
|
* @param insInfoSite The INS-InfoSize (IP:PORT)
|
||||||
|
* @param insFrontendSite The INS-InfoSize (IP:PORT)
|
||||||
|
* @throws IOException If an I/O error occurs.
|
||||||
|
* @throws CertificateException If a certificate error occurs.
|
||||||
|
*/
|
||||||
|
public ProtocolINSServer(String insInfoSite, String insFrontendSite) throws Exception {
|
||||||
|
super("ins", "ins");
|
||||||
|
this.insInfoSite = insInfoSite;
|
||||||
|
this.insFrontendSite = insFrontendSite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String getInsInfoSite() {
|
||||||
|
return insInfoSite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String getInsFrontendSite() {
|
||||||
|
return insFrontendSite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start(int tcpPort) throws IOException, InterruptedException, NoSuchAlgorithmException {
|
||||||
|
getNetwork().start(tcpPort, -1);
|
||||||
|
|
||||||
|
String caPrefix = getFolderStructure().getCaPrefix() + NetworkUtils.getPublicIPAddress();
|
||||||
|
File caPemFile = new File(getFolderStructure().publicCAFolder, caPrefix + ".pem");
|
||||||
|
|
||||||
|
byte[] caBytes = FileUtils.readFileFull(caPemFile).getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||||
|
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256");
|
||||||
|
String fp = java.util.HexFormat.of().formatHex(md.digest(caBytes));
|
||||||
|
|
||||||
|
getProtocolBridge().getProtocolValues().logger.info("CA Fingerprint: " + fp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -188,7 +95,8 @@ public abstract class ProtocolINSServer extends DefaultMethodsOverrider {
|
|||||||
* @param sub An optional subname, or {@code null}.
|
* @param sub An optional subname, or {@code null}.
|
||||||
* @param type The record type requested.
|
* @param type The record type requested.
|
||||||
*/
|
*/
|
||||||
public void onQueryReceived(String tln, String name, String sub, INSRecordType type) {}
|
public void onQueryReceived(String tln, String name, String sub, INSRecordType type) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback fired after an INS response was successfully sent to the client.
|
* Callback fired after an INS response was successfully sent to the client.
|
||||||
@@ -199,7 +107,8 @@ public abstract class ProtocolINSServer extends DefaultMethodsOverrider {
|
|||||||
* @param type The requested record type.
|
* @param type The requested record type.
|
||||||
* @param results The records returned to the client.
|
* @param results The records returned to the client.
|
||||||
*/
|
*/
|
||||||
public void onResponseSent(String tln, String name, String sub, INSRecordType type, List<INSRecord> results) {}
|
public void onResponseSent(String tln, String name, String sub, INSRecordType type, List<INSRecord> results) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback fired when an INS response could not be delivered to the client.
|
* Callback fired when an INS response could not be delivered to the client.
|
||||||
@@ -211,7 +120,8 @@ public abstract class ProtocolINSServer extends DefaultMethodsOverrider {
|
|||||||
* @param results The records that would have been sent.
|
* @param results The records that would have been sent.
|
||||||
* @param exception The exception describing the failure.
|
* @param exception The exception describing the failure.
|
||||||
*/
|
*/
|
||||||
public void onResponseSentFailed(String tln, String name, String sub, INSRecordType type, List<INSRecord> results, Exception exception) {}
|
public void onResponseSentFailed(String tln, String name, String sub, INSRecordType type, List<INSRecord> results, Exception exception) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the information endpoint for a given Top-Level Name (TLN).
|
* Resolves the information endpoint for a given Top-Level Name (TLN).
|
||||||
@@ -239,60 +149,8 @@ public abstract class ProtocolINSServer extends DefaultMethodsOverrider {
|
|||||||
*
|
*
|
||||||
* @param tln the top-level name for which the info site should be resolved.
|
* @param tln the top-level name for which the info site should be resolved.
|
||||||
* Must not be null. Case-insensitive.
|
* Must not be null. Case-insensitive.
|
||||||
*
|
|
||||||
* @return a string in <code>"host:port"</code> format representing the TLN's info endpoint,
|
* @return a string in <code>"host:port"</code> format representing the TLN's info endpoint,
|
||||||
* or <code>null</code> if the TLN has no registered info site.
|
* or <code>null</code> if the TLN has no registered info site.
|
||||||
*/
|
*/
|
||||||
public abstract String resolveTLNInfoSite(String tln);
|
public abstract String resolveTLNInfoSite(String tln);
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the INS registration site URL from the configuration.
|
|
||||||
*
|
|
||||||
* @return The INS registration site URL.
|
|
||||||
*/
|
|
||||||
public final String getINSFrontendSite() {
|
|
||||||
return configurationManager.getString("server.site.frontend");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class representing the folder structure for server certificates.
|
|
||||||
*/
|
|
||||||
public final class ServerCertificateFolderStructure {
|
|
||||||
public final File certificatesFolder;
|
|
||||||
|
|
||||||
public final File publicFolder;
|
|
||||||
public final File privateFolder;
|
|
||||||
|
|
||||||
public final File privateCAFolder;
|
|
||||||
public final File privateServerFolder;
|
|
||||||
|
|
||||||
public final File publicCAFolder;
|
|
||||||
public final File publicServerFolder;
|
|
||||||
public final String caPrefix = "ca_ins_";
|
|
||||||
public final String certPrefix = "cert_ins_";
|
|
||||||
|
|
||||||
public ServerCertificateFolderStructure() {
|
|
||||||
certificatesFolder = new File("certificates");
|
|
||||||
|
|
||||||
publicFolder = new File(certificatesFolder, "public");
|
|
||||||
privateFolder = new File(certificatesFolder, "private");
|
|
||||||
|
|
||||||
privateCAFolder = new File(privateFolder, "ca");
|
|
||||||
privateServerFolder = new File(privateFolder, "server");
|
|
||||||
|
|
||||||
publicCAFolder = new File(publicFolder, "ca");
|
|
||||||
publicServerFolder = new File(publicFolder, "server");
|
|
||||||
|
|
||||||
if (!certificatesFolder.exists()) certificatesFolder.mkdirs();
|
|
||||||
|
|
||||||
if (!publicFolder.exists()) publicFolder.mkdirs();
|
|
||||||
if (!privateFolder.exists()) privateFolder.mkdirs();
|
|
||||||
|
|
||||||
if (!privateCAFolder.exists()) privateCAFolder.mkdirs();
|
|
||||||
if (!privateServerFolder.exists()) privateServerFolder.mkdirs();
|
|
||||||
|
|
||||||
if (!publicCAFolder.exists()) publicCAFolder.mkdirs();
|
|
||||||
if (!publicServerFolder.exists()) publicServerFolder.mkdirs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.side.ins.events;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
|
||||||
import org.openautonomousconnection.protocol.side.ins.ConnectedProtocolClient;
|
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event triggered when a protocol client connects to the INS server.
|
|
||||||
*/
|
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
|
|
||||||
public final class ConnectedProtocolClientEvent extends Event {
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final ConnectedProtocolClient protocolClient;
|
|
||||||
|
|
||||||
public ConnectedProtocolClientEvent(ConnectedProtocolClient protocolClient) {
|
|
||||||
this.protocolClient = protocolClient;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +1,56 @@
|
|||||||
package org.openautonomousconnection.protocol.side.ins;
|
package org.openautonomousconnection.protocol.side.server;
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler;
|
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectedClient;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.client.S_ClientDisconnectedEvent;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
|
||||||
import org.openautonomousconnection.protocol.packets.OACPacket;
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
/**
|
public class CustomConnectedClient extends EventListener {
|
||||||
* Represents a connected protocol client on the INS server side.
|
|
||||||
*/
|
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
|
|
||||||
public final class ConnectedProtocolClient {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The connection handler associated with this protocol client.
|
|
||||||
*/
|
|
||||||
@Getter
|
@Getter
|
||||||
private final ConnectionHandler connectionHandler;
|
private final ConnectedClient connection;
|
||||||
|
|
||||||
/**
|
|
||||||
* The Protocol Server associated with this protocol client.
|
|
||||||
*/
|
|
||||||
@Getter
|
@Getter
|
||||||
private final ProtocolINSServer protocolINSServer;
|
private final ProtocolCustomServer server;
|
||||||
|
|
||||||
/**
|
|
||||||
* The protocol version of the connected client.
|
|
||||||
*/
|
|
||||||
private ProtocolVersion clientVersion = null;
|
private ProtocolVersion clientVersion = null;
|
||||||
|
|
||||||
public ConnectedProtocolClient(ConnectionHandler connectionHandler, ProtocolINSServer protocolINSServer) {
|
@Getter
|
||||||
this.connectionHandler = connectionHandler;
|
private boolean clientVersionLoaded = false;
|
||||||
this.protocolINSServer = protocolINSServer;
|
|
||||||
|
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.
|
* Gets the protocol version of the connected client.
|
||||||
* Defaults to PV_1_0_0_CLASSIC if not set.
|
|
||||||
*
|
*
|
||||||
* @return The protocol version of the client.
|
* @return The protocol version of the client, defaults to PV_1_0_0_CLASSIC if not set.
|
||||||
*/
|
*/
|
||||||
public ProtocolVersion getClientVersion() {
|
public ProtocolVersion getClientVersion() {
|
||||||
return clientVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : clientVersion;
|
return clientVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : clientVersion;
|
||||||
@@ -50,7 +62,11 @@ public final class ConnectedProtocolClient {
|
|||||||
* @param clientVersion The protocol version to set.
|
* @param clientVersion The protocol version to set.
|
||||||
*/
|
*/
|
||||||
public void setClientVersion(ProtocolVersion clientVersion) {
|
public void setClientVersion(ProtocolVersion clientVersion) {
|
||||||
if (clientVersion == null) this.clientVersion = clientVersion;
|
if (clientVersionLoaded) return;
|
||||||
|
if (this.clientVersion != null) return;
|
||||||
|
|
||||||
|
this.clientVersion = clientVersion;
|
||||||
|
this.clientVersionLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -113,11 +129,9 @@ public final class ConnectedProtocolClient {
|
|||||||
* @return True if the client is classic, false otherwise.
|
* @return True if the client is classic, false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isClassicClient() {
|
public boolean isClassicClient() {
|
||||||
// Check if the server version is classic
|
|
||||||
return getClientVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
return getClientVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the connected client supports classic protocol versions.
|
* Checks if the connected client supports classic protocol versions.
|
||||||
*
|
*
|
||||||
@@ -136,30 +150,36 @@ public final class ConnectedProtocolClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the connected client supports the given packet's protocol version.
|
* Checks if the connected client supports the protocol version of the given packet.
|
||||||
*
|
*
|
||||||
* @param packet The packet to check.
|
* @param packet The packet to check support for.
|
||||||
* @return True if the client supports the packet's protocol version, false otherwise.
|
* @return True if the client supports the packet's protocol version, false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean supportClientPacket(OACPacket packet) {
|
public boolean supportClientPacket(OACPacket packet) {
|
||||||
return supportClientVersion(packet.getProtocolVersion());
|
boolean compatible = false;
|
||||||
|
|
||||||
|
for (ProtocolVersion compatibleVersion : packet.getCompatibleVersions()) {
|
||||||
|
if (!compatible) compatible = supportClientVersion(compatibleVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
return compatible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the connected client supports the given protocol version.
|
* Checks if the connected client supports the given protocol version.
|
||||||
*
|
*
|
||||||
* @param targetVersion The protocol version to check.
|
* @param targetVersion The protocol version to check support for.
|
||||||
* @return True if the client supports the target version, false otherwise.
|
* @return True if the client supports the target version, false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean supportClientVersion(ProtocolVersion targetVersion) {
|
public boolean supportClientVersion(ProtocolVersion targetVersion) {
|
||||||
// Check if the client version matches or is compatible with the target version
|
// Check if the client version matches the target version or is compatible
|
||||||
return getClientVersion() == targetVersion || getClientVersion().getCompatibleVersions().contains(targetVersion);
|
return getClientVersion() == targetVersion || getClientVersion().getCompatibleVersions().contains(targetVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the connected client supports the given protocol.
|
* Checks if the connected client supports the given protocol.
|
||||||
*
|
*
|
||||||
* @param protocol The protocol to check.
|
* @param protocol The protocol to check support for.
|
||||||
* @return True if the client supports the protocol, false otherwise.
|
* @return True if the client supports the protocol, false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean supportClientProtocol(ProtocolVersion.Protocol protocol) {
|
public boolean supportClientProtocol(ProtocolVersion.Protocol protocol) {
|
||||||
@@ -174,3 +194,4 @@ public final class ConnectedProtocolClient {
|
|||||||
return getClientVersion().getSupportedProtocols().contains(protocol) || yes;
|
return getClientVersion().getSupportedProtocols().contains(protocol) || yes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,332 @@
|
|||||||
|
package org.openautonomousconnection.protocol.side.server;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.client.S_ClientDisconnectedEvent;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.ServerStoppedEvent;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.utils.PemUtils;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.events.S_CustomClientDisconnectedEvent;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public abstract class ProtocolCustomServer extends EventListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure for server.
|
||||||
|
*/
|
||||||
|
private final ServerCertificateFolderStructure folderStructure;
|
||||||
|
/**
|
||||||
|
* Certificate files for SSL.
|
||||||
|
*/
|
||||||
|
private final File certFile;
|
||||||
|
/**
|
||||||
|
* Certificate files for SSL.
|
||||||
|
*/
|
||||||
|
private final File keyFile;
|
||||||
|
/**
|
||||||
|
* List of connected web clients.
|
||||||
|
*/
|
||||||
|
private final List<CustomConnectedClient> clients;
|
||||||
|
/**
|
||||||
|
* The reference to the ProtocolBridge Object
|
||||||
|
*/
|
||||||
|
private ProtocolBridge protocolBridge;
|
||||||
|
/**
|
||||||
|
* The network server handling pipeline connections.
|
||||||
|
*/
|
||||||
|
private NetworkServer network;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the web server with the given configuration, authentication, and rules files.
|
||||||
|
*
|
||||||
|
* @throws Exception If an error occurs during initialization.
|
||||||
|
*/
|
||||||
|
public ProtocolCustomServer(String caPrefix, String certPrefix) throws Exception {
|
||||||
|
// Initialize the list of connected clients
|
||||||
|
this.clients = new ArrayList<>();
|
||||||
|
|
||||||
|
// Set up folder structure for certificates
|
||||||
|
folderStructure = new ServerCertificateFolderStructure(caPrefix, certPrefix);
|
||||||
|
|
||||||
|
// Check for necessary certificate files
|
||||||
|
checkFileExists(folderStructure.publicServerFolder, folderStructure.certPrefix, ".crt");
|
||||||
|
checkFileExists(folderStructure.privateServerFolder, folderStructure.certPrefix, ".key");
|
||||||
|
|
||||||
|
// Set up certificate files based on public IP address
|
||||||
|
this.certFile = new File(folderStructure.publicServerFolder, folderStructure.certPrefix + NetworkUtils.getPublicIPAddress() + ".crt");
|
||||||
|
this.keyFile = new File(folderStructure.privateServerFolder, folderStructure.certPrefix + NetworkUtils.getPublicIPAddress() + ".key");
|
||||||
|
}
|
||||||
|
|
||||||
|
public final ServerCertificateFolderStructure getFolderStructure() {
|
||||||
|
return folderStructure;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final ProtocolBridge getProtocolBridge() {
|
||||||
|
return protocolBridge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final List<CustomConnectedClient> getClients() {
|
||||||
|
return clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final NetworkServer getNetwork() {
|
||||||
|
return network;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void setNetwork(NetworkServer network) {
|
||||||
|
this.network = network;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects the ProtocolBridge.
|
||||||
|
*
|
||||||
|
* @param bridge the Bridge instance.
|
||||||
|
* @throws IOException when NetworkServer failed to create.
|
||||||
|
*/
|
||||||
|
public final void attachBridge(ProtocolBridge bridge, String keyPassword, boolean ssl, ClientAuthMode authMode) throws Exception {
|
||||||
|
if (this.protocolBridge != null)
|
||||||
|
throw new IllegalStateException("ProtocolBridge already attached!");
|
||||||
|
|
||||||
|
this.protocolBridge = bridge;
|
||||||
|
bridge.getProtocolValues().eventManager.registerListener(this);
|
||||||
|
|
||||||
|
if (ssl) {
|
||||||
|
char[] keyPass = new char[0];
|
||||||
|
if (keyPassword != null) keyPass = keyPassword.toCharArray();
|
||||||
|
|
||||||
|
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
trustStore.load(null, null);
|
||||||
|
|
||||||
|
int caIndex = 0;
|
||||||
|
for (File caPem : FileUtils.listFiles(folderStructure.publicCAFolder, ".pem", ".crt", ".cer")) {
|
||||||
|
if (caPem.getName().endsWith(".srl")) continue;
|
||||||
|
if (!caPem.getName().startsWith(folderStructure.getCaPrefix())) continue;
|
||||||
|
|
||||||
|
X509Certificate caCert = PemUtils.loadCertificate(caPem);
|
||||||
|
trustStore.setCertificateEntry("root-ca-" + (caIndex++), caCert);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build server key material (private key + certificate chain)
|
||||||
|
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
keyStore.load(null, null);
|
||||||
|
|
||||||
|
Map<String, File> keyFiles = new HashMap<>();
|
||||||
|
Map<String, File> certFiles = new HashMap<>();
|
||||||
|
|
||||||
|
for (File f : FileUtils.listFiles(folderStructure.privateServerFolder, ".key")) {
|
||||||
|
if (f.getName().endsWith(".srl")) continue;
|
||||||
|
if (!f.getName().startsWith(folderStructure.getCertPrefix())) continue;
|
||||||
|
keyFiles.put(FileUtils.stripExt(f.getName()), f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow cert files in public server folder as .pem/.crt/.cer
|
||||||
|
for (File f : FileUtils.listFiles(folderStructure.publicServerFolder, ".pem", ".crt", ".cer")) {
|
||||||
|
if (f.getName().endsWith(".srl")) continue;
|
||||||
|
if (!f.getName().startsWith(folderStructure.getCertPrefix())) continue;
|
||||||
|
certFiles.put(FileUtils.stripExt(f.getName()), f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load all CA certs once (for chain building)
|
||||||
|
List<X509Certificate> caCerts = new ArrayList<>();
|
||||||
|
for (File caPem : FileUtils.listFiles(folderStructure.publicCAFolder, ".pem", ".crt", ".cer")) {
|
||||||
|
if (caPem.getName().endsWith(".srl")) continue;
|
||||||
|
if (!caPem.getName().startsWith(folderStructure.getCaPrefix())) continue;
|
||||||
|
caCerts.add(PemUtils.loadCertificate(caPem));
|
||||||
|
}
|
||||||
|
|
||||||
|
int serverIndex = 0;
|
||||||
|
for (Map.Entry<String, File> e : keyFiles.entrySet()) {
|
||||||
|
String base = e.getKey();
|
||||||
|
File keyFile = e.getValue();
|
||||||
|
|
||||||
|
File certFile = certFiles.get(base);
|
||||||
|
if (certFile == null) {
|
||||||
|
// If your naming differs between private key and public cert, log it and skip
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrivateKey privateKey = PemUtils.loadPrivateKey(keyFile);
|
||||||
|
X509Certificate leaf = PemUtils.loadCertificate(certFile);
|
||||||
|
|
||||||
|
// Build a minimal chain: leaf + issuer CA if found (or all CAs as fallback)
|
||||||
|
List<X509Certificate> chain = new ArrayList<>();
|
||||||
|
chain.add(leaf);
|
||||||
|
|
||||||
|
// Try to find matching issuer CA(s) by Subject/Issuer DN
|
||||||
|
X509Certificate current = leaf;
|
||||||
|
for (int depth = 0; depth < 5; depth++) {
|
||||||
|
X509Certificate issuer = findIssuer(current, caCerts);
|
||||||
|
if (issuer == null) break;
|
||||||
|
chain.add(issuer);
|
||||||
|
if (issuer.getSubjectX500Principal().equals(issuer.getIssuerX500Principal())) {
|
||||||
|
// self-signed root reached
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
current = issuer;
|
||||||
|
}
|
||||||
|
|
||||||
|
String alias = "server-" + (serverIndex++);
|
||||||
|
keyStore.setKeyEntry(alias, privateKey, keyPass, chain.toArray(new X509Certificate[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If still empty, fail fast with a clear error instead of handshake_failure later
|
||||||
|
if (!keyStore.aliases().hasMoreElements()) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"No server key entries loaded. Ensure private .key and public .crt/.pem names match and start with prefix "
|
||||||
|
+ folderStructure.getCertPrefix()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
network = new NetworkServer.Builder()
|
||||||
|
.packetHandler(bridge.getProtocolValues().packetHandler)
|
||||||
|
.eventManager(bridge.getProtocolValues().eventManager)
|
||||||
|
.sslEnabled(true)
|
||||||
|
.clientAuthMode(authMode)
|
||||||
|
.keyStore(keyStore, keyPass)
|
||||||
|
.trustStore(trustStore)
|
||||||
|
.build();
|
||||||
|
} else {
|
||||||
|
network = new NetworkServer.Builder()
|
||||||
|
.packetHandler(bridge.getProtocolValues().packetHandler)
|
||||||
|
.eventManager(bridge.getProtocolValues().eventManager)
|
||||||
|
.sslEnabled(false)
|
||||||
|
.clientAuthMode(ClientAuthMode.NONE)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
|
public final void onStop(ServerStoppedEvent event) {
|
||||||
|
if (event.getServer() == network) {
|
||||||
|
protocolBridge.getProtocolValues().eventManager.unregisterListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a connected web client by its client ID.
|
||||||
|
*
|
||||||
|
* @param clientID The client ID to search for.
|
||||||
|
* @return The connected web client with the specified ID, or null if not found.
|
||||||
|
*/
|
||||||
|
public final CustomConnectedClient getClientByID(UUID clientID) {
|
||||||
|
for (CustomConnectedClient client : clients)
|
||||||
|
if (client.getConnection().getUniqueID().equals(clientID)) return client;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to find the issuer certificate of {@code cert} within {@code candidates}.
|
||||||
|
*
|
||||||
|
* @param cert the certificate whose issuer should be found
|
||||||
|
* @param candidates possible issuer certificates
|
||||||
|
* @return issuer certificate if found, otherwise null
|
||||||
|
*/
|
||||||
|
private X509Certificate findIssuer(X509Certificate cert, List<X509Certificate> candidates) {
|
||||||
|
for (X509Certificate ca : candidates) {
|
||||||
|
if (cert.getIssuerX500Principal().equals(ca.getSubjectX500Principal())) {
|
||||||
|
return ca;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the required certificate files exist in the specified folder.
|
||||||
|
*
|
||||||
|
* @param folder The folder to check for certificate files.
|
||||||
|
* @param prefix The prefix of the certificate files.
|
||||||
|
* @param extension The extension of the certificate files.
|
||||||
|
* @throws CertificateException If a required certificate file is missing or invalid.
|
||||||
|
* @throws IOException If an I/O error occurs while checking the files.
|
||||||
|
*/
|
||||||
|
private void checkFileExists(File folder, String prefix, String extension) throws CertificateException, IOException {
|
||||||
|
// Ensure the folder exists
|
||||||
|
if (folder == null) throw new FileNotFoundException("Folder does not exist");
|
||||||
|
|
||||||
|
// List all files in the folder
|
||||||
|
File[] files = folder.listFiles();
|
||||||
|
if (files == null || files.length == 0)
|
||||||
|
throw new FileNotFoundException("Folder " + folder.getAbsolutePath() + " is empty");
|
||||||
|
|
||||||
|
// Check for the required certificate file
|
||||||
|
for (File file : files) {
|
||||||
|
if (!file.getName().startsWith(prefix))
|
||||||
|
throw new CertificateException(file.getAbsolutePath() + " is not valid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
|
public final void clientDisconnected(S_ClientDisconnectedEvent event) {
|
||||||
|
for (CustomConnectedClient client : new ArrayList<>(clients)) {
|
||||||
|
if (client.getConnection().getUniqueID().equals(event.getClient().getUniqueID())) {
|
||||||
|
protocolBridge.getProtocolValues().eventManager.executeEvent(new S_CustomClientDisconnectedEvent(client));
|
||||||
|
clients.remove(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the folder structure for server certificates.
|
||||||
|
*/
|
||||||
|
public final class ServerCertificateFolderStructure {
|
||||||
|
public final File certificatesFolder;
|
||||||
|
|
||||||
|
public final File publicFolder;
|
||||||
|
public final File privateFolder;
|
||||||
|
|
||||||
|
public final File privateCAFolder;
|
||||||
|
public final File privateServerFolder;
|
||||||
|
|
||||||
|
public final File publicCAFolder;
|
||||||
|
public final File publicServerFolder;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private String caPrefix = "ca_server_";
|
||||||
|
@Getter
|
||||||
|
private String certPrefix = "cert_server_";
|
||||||
|
|
||||||
|
public ServerCertificateFolderStructure(String caPrefix, String certPrefix) {
|
||||||
|
this.caPrefix = "ca_" + caPrefix + "_";
|
||||||
|
this.certPrefix = "cert_" + certPrefix + "_";
|
||||||
|
|
||||||
|
certificatesFolder = new File("certificates");
|
||||||
|
|
||||||
|
publicFolder = new File(certificatesFolder, "public");
|
||||||
|
privateFolder = new File(certificatesFolder, "private");
|
||||||
|
|
||||||
|
privateCAFolder = new File(privateFolder, "ca");
|
||||||
|
privateServerFolder = new File(privateFolder, "server");
|
||||||
|
|
||||||
|
publicCAFolder = new File(publicFolder, "ca");
|
||||||
|
publicServerFolder = new File(publicFolder, "server");
|
||||||
|
|
||||||
|
if (!certificatesFolder.exists()) certificatesFolder.mkdirs();
|
||||||
|
|
||||||
|
if (!publicFolder.exists()) publicFolder.mkdirs();
|
||||||
|
if (!privateFolder.exists()) privateFolder.mkdirs();
|
||||||
|
|
||||||
|
if (!privateCAFolder.exists()) privateCAFolder.mkdirs();
|
||||||
|
if (!privateServerFolder.exists()) privateServerFolder.mkdirs();
|
||||||
|
|
||||||
|
if (!publicCAFolder.exists()) publicCAFolder.mkdirs();
|
||||||
|
if (!publicServerFolder.exists()) publicServerFolder.mkdirs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.openautonomousconnection.protocol.side.server.events;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event triggered when a web client connects to the web server.
|
||||||
|
*/
|
||||||
|
public final class S_CustomClientConnectedEvent extends Event {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The connected web client.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final CustomConnectedClient client;
|
||||||
|
|
||||||
|
public S_CustomClientConnectedEvent(CustomConnectedClient client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.openautonomousconnection.protocol.side.server.events;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event triggered when a web client connects to the web server.
|
||||||
|
*/
|
||||||
|
public final class S_CustomClientDisconnectedEvent extends Event {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The connected web client.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final CustomConnectedClient client;
|
||||||
|
|
||||||
|
public S_CustomClientDisconnectedEvent(CustomConnectedClient client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,734 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.side.web;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
|
||||||
import org.openautonomousconnection.protocol.packets.OACPacket;
|
|
||||||
import org.openautonomousconnection.protocol.side.web.managers.AuthManager;
|
|
||||||
import org.openautonomousconnection.protocol.side.web.managers.RuleManager;
|
|
||||||
import org.openautonomousconnection.protocol.side.web.managers.SessionManager;
|
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLSocket;
|
|
||||||
import java.io.*;
|
|
||||||
import java.net.URLDecoder;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a connected web client.
|
|
||||||
* Manages the connection, handles HTTP requests, and serves files.
|
|
||||||
*/
|
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
|
||||||
public final class ConnectedWebClient {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The connection handler associated with this web client.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private final ConnectionHandler pipelineConnection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The SSL socket for the web client connection.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private SSLSocket webSocket;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The output stream for sending data to the client.
|
|
||||||
*/
|
|
||||||
private ObjectOutputStream outputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The input stream for receiving data from the client.
|
|
||||||
*/
|
|
||||||
private ObjectInputStream inputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The protocol version of the connected client.
|
|
||||||
*/
|
|
||||||
private ProtocolVersion clientVersion = null;
|
|
||||||
/**
|
|
||||||
* Indicates if the client version has been loaded.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private boolean clientVersionLoaded = false;
|
|
||||||
/**
|
|
||||||
* The reference to the ProtocolWebServer Object
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private ProtocolWebServer protocolWebServer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a ConnectedWebClient with the given connection handler.
|
|
||||||
*
|
|
||||||
* @param pipelineConnection The connection handler for the web client.
|
|
||||||
*/
|
|
||||||
public ConnectedWebClient(ConnectionHandler pipelineConnection) {
|
|
||||||
this.pipelineConnection = pipelineConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an HTTP redirect response to the client.
|
|
||||||
*
|
|
||||||
* @param out The output stream to send the response to.
|
|
||||||
* @param location The URL to redirect to.
|
|
||||||
* @param cookies Optional cookies to set in the response.
|
|
||||||
* @throws IOException If an I/O error occurs.
|
|
||||||
*/
|
|
||||||
private static void sendRedirect(OutputStream out, String location, Map<String, String> cookies) throws IOException {
|
|
||||||
// Send HTTP 302 Found response with Location header
|
|
||||||
out.write(("OAC 302 Found\r\n").getBytes());
|
|
||||||
out.write(("Location: " + location + "\r\n").getBytes());
|
|
||||||
|
|
||||||
// Set cookies if provided
|
|
||||||
if (cookies != null) {
|
|
||||||
for (var entry : cookies.entrySet()) {
|
|
||||||
out.write((entry.getKey() + ": " + entry.getValue() + "\r\n").getBytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// End of headers
|
|
||||||
out.write("\r\n".getBytes());
|
|
||||||
out.flush();
|
|
||||||
} /**
|
|
||||||
* Thread for receiving data from the client.
|
|
||||||
*/
|
|
||||||
private final Thread receiveThread = new Thread(this::receive);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses POST parameters from the input stream.
|
|
||||||
*
|
|
||||||
* @param in The input stream to read from.
|
|
||||||
* @return A map of POST parameter names to values.
|
|
||||||
* @throws IOException If an I/O error occurs.
|
|
||||||
*/
|
|
||||||
private static Map<String, String> parsePostParams(InputStream in) throws IOException {
|
|
||||||
// Read the entire input stream into a string
|
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
while (reader.ready()) {
|
|
||||||
sb.append((char) reader.read());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split the string into key-value pairs and decode them
|
|
||||||
Map<String, String> map = new HashMap<>();
|
|
||||||
String[] pairs = sb.toString().split("&");
|
|
||||||
for (String p : pairs) {
|
|
||||||
// Split each pair into key and value
|
|
||||||
String[] kv = p.split("=", 2);
|
|
||||||
if (kv.length == 2)
|
|
||||||
// Decode and store in the map
|
|
||||||
map.put(URLDecoder.decode(kv[0], StandardCharsets.UTF_8), URLDecoder.decode(kv[1], StandardCharsets.UTF_8));
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalizes a file path to prevent directory traversal attacks.
|
|
||||||
*
|
|
||||||
* @param path The raw file path.
|
|
||||||
* @return The normalized file path.
|
|
||||||
*/
|
|
||||||
private static String normalizePath(String path) {
|
|
||||||
// Replace backslashes with forward slashes and remove ".." segments
|
|
||||||
path = path.replace("/", File.separator).replace("\\", "/");
|
|
||||||
// Remove any ".." segments to prevent directory traversal
|
|
||||||
while (path.contains("..")) path = path.replace("..", "");
|
|
||||||
|
|
||||||
// Remove leading slashes
|
|
||||||
if (path.startsWith("/")) path = path.substring(1);
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses query parameters from a raw URL path.
|
|
||||||
*
|
|
||||||
* @param rawPath The raw URL path containing query parameters.
|
|
||||||
* @return A map of query parameter names to values.
|
|
||||||
*/
|
|
||||||
private static Map<String, String> parseQueryParams(String rawPath) {
|
|
||||||
// Extract query parameters from the URL path
|
|
||||||
Map<String, String> map = new HashMap<>();
|
|
||||||
if (rawPath.contains("?")) {
|
|
||||||
// Split the query string into key-value pairs
|
|
||||||
String[] params = rawPath.substring(rawPath.indexOf("?") + 1).split("&");
|
|
||||||
for (String p : params) {
|
|
||||||
// Split each pair into key and value
|
|
||||||
String[] kv = p.split("=");
|
|
||||||
if (kv.length == 2) map.put(kv[0], kv[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the request is a multipart/form-data request.
|
|
||||||
*
|
|
||||||
* @param headers The HTTP headers of the request.
|
|
||||||
* @return True if the request is multipart/form-data, false otherwise.
|
|
||||||
*/
|
|
||||||
private static boolean isMultipart(Map<String, String> headers) {
|
|
||||||
String contentType = headers.get("content-type");
|
|
||||||
return contentType != null && contentType.startsWith("multipart/form-data");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles a multipart/form-data request, saving uploaded files to the specified directory.
|
|
||||||
*
|
|
||||||
* @param in The input stream to read the request body from.
|
|
||||||
* @param headers The HTTP headers of the request.
|
|
||||||
* @param uploadDir The directory to save uploaded files to.
|
|
||||||
* @throws IOException If an I/O error occurs.
|
|
||||||
*/
|
|
||||||
private static void handleMultipart(InputStream in, Map<String, String> headers, File uploadDir) throws IOException {
|
|
||||||
// Ensure the upload directory exists
|
|
||||||
if (!uploadDir.exists()) uploadDir.mkdirs();
|
|
||||||
|
|
||||||
// Extract the boundary from the Content-Type header
|
|
||||||
String contentType = headers.get("content-type");
|
|
||||||
String boundary = "--" + contentType.split("boundary=")[1];
|
|
||||||
|
|
||||||
// Read the entire request body into a buffer
|
|
||||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
|
||||||
byte[] lineBuffer = new byte[8192];
|
|
||||||
int read;
|
|
||||||
while ((read = in.read(lineBuffer)) != -1) {
|
|
||||||
buffer.write(lineBuffer, 0, read);
|
|
||||||
if (buffer.size() > 10 * 1024 * 1024) break; // 10 MB max
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the multipart data
|
|
||||||
String data = buffer.toString(StandardCharsets.UTF_8);
|
|
||||||
String[] parts = data.split(boundary);
|
|
||||||
|
|
||||||
// Process each part
|
|
||||||
for (String part : parts) {
|
|
||||||
if (part.contains("Content-Disposition")) {
|
|
||||||
String name = null;
|
|
||||||
String filename = null;
|
|
||||||
|
|
||||||
// Extract headers from the part
|
|
||||||
for (String headerLine : part.split("\r\n")) {
|
|
||||||
if (headerLine.startsWith("Content-Disposition")) {
|
|
||||||
if (headerLine.contains("filename=\"")) {
|
|
||||||
int start = headerLine.indexOf("filename=\"") + 10;
|
|
||||||
int end = headerLine.indexOf("\"", start);
|
|
||||||
filename = headerLine.substring(start, end);
|
|
||||||
}
|
|
||||||
if (headerLine.contains("name=\"")) {
|
|
||||||
int start = headerLine.indexOf("name=\"") + 6;
|
|
||||||
int end = headerLine.indexOf("\"", start);
|
|
||||||
name = headerLine.substring(start, end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the file if a filename is provided
|
|
||||||
if (filename != null && !filename.isEmpty()) {
|
|
||||||
int headerEnd = part.indexOf("\r\n\r\n");
|
|
||||||
byte[] fileData = part.substring(headerEnd + 4).getBytes(StandardCharsets.UTF_8);
|
|
||||||
File outFile = new File(uploadDir, filename);
|
|
||||||
Files.write(outFile.toPath(), fileData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an response to the client.
|
|
||||||
*
|
|
||||||
* @param out
|
|
||||||
* @param code
|
|
||||||
* @param file
|
|
||||||
* @param headers
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
private static void sendResponse(OutputStream out, int code, File file, Map<String, String> headers) throws IOException {
|
|
||||||
byte[] body = Files.readAllBytes(file.toPath());
|
|
||||||
sendResponse(out, code, body, "text/html", headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an response to the client.
|
|
||||||
*
|
|
||||||
* @param out The output stream to send the response to.
|
|
||||||
* @param code The HTTP status code.
|
|
||||||
* @param file The file to read the response body from.
|
|
||||||
* @throws IOException If an I/O error occurs.
|
|
||||||
*/
|
|
||||||
private static void sendResponse(OutputStream out, int code, File file) throws IOException {
|
|
||||||
sendResponse(out, code, Files.readString(file.toPath()), "text/html");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an response to the client.
|
|
||||||
*
|
|
||||||
* @param out The output stream to send the response to.
|
|
||||||
* @param code The HTTP status code.
|
|
||||||
* @param body The response body as a string.
|
|
||||||
* @param contentType The content type of the response.
|
|
||||||
* @throws IOException If an I/O error occurs.
|
|
||||||
*/
|
|
||||||
private static void sendResponse(OutputStream out, int code, String body, String contentType) throws IOException {
|
|
||||||
sendResponse(out, code, body.getBytes(StandardCharsets.UTF_8), contentType, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an response to the client.
|
|
||||||
*
|
|
||||||
* @param out The output stream to send the response to.
|
|
||||||
* @param code The HTTP status code.
|
|
||||||
* @param file The file to read the response body from.
|
|
||||||
* @param contentType The content type of the response.
|
|
||||||
* @throws IOException If an I/O error occurs.
|
|
||||||
*/
|
|
||||||
private static void sendResponse(OutputStream out, int code, File file, String contentType) throws IOException {
|
|
||||||
byte[] bytes = Files.readAllBytes(file.toPath());
|
|
||||||
sendResponse(out, code, bytes, contentType, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an response to the client.
|
|
||||||
*
|
|
||||||
* @param out The output stream to send the response to.
|
|
||||||
* @param code The HTTP status code.
|
|
||||||
* @param body The response body as a byte array.
|
|
||||||
* @param contentType The content type of the response.
|
|
||||||
* @param headers Additional headers to include in the response.
|
|
||||||
* @throws IOException If an I/O error occurs.
|
|
||||||
*/
|
|
||||||
private static void sendResponse(OutputStream out, int code, byte[] body, String contentType, Map<String, String> headers) throws IOException {
|
|
||||||
// Send response status line and headers
|
|
||||||
out.write(("OAC " + code + " " + getStatusText(code) + "\r\n").getBytes());
|
|
||||||
out.write(("Content-Type: " + contentType + "\r\n").getBytes());
|
|
||||||
out.write(("Content-Length: " + body.length + "\r\n").getBytes());
|
|
||||||
|
|
||||||
// Write additional headers if provided
|
|
||||||
if (headers != null) headers.forEach((k, v) -> {
|
|
||||||
try {
|
|
||||||
out.write((k + ": " + v + "\r\n").getBytes());
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// End of headers
|
|
||||||
out.write("\r\n".getBytes());
|
|
||||||
out.write(body);
|
|
||||||
out.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the standard status text for a given status code.
|
|
||||||
*
|
|
||||||
* @param code The status code.
|
|
||||||
* @return The corresponding status text.
|
|
||||||
*/
|
|
||||||
private static String getStatusText(int code) {
|
|
||||||
return switch (code) {
|
|
||||||
case 200 -> "OK";
|
|
||||||
case 301 -> "Moved Permanently";
|
|
||||||
case 302 -> "Found";
|
|
||||||
case 400 -> "Bad Request";
|
|
||||||
case 401 -> "Unauthorized";
|
|
||||||
case 403 -> "Forbidden";
|
|
||||||
case 404 -> "Not Found";
|
|
||||||
case 500 -> "Internal Server Error";
|
|
||||||
default -> "Unknown";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the content type based on the file extension.
|
|
||||||
*
|
|
||||||
* @param name The file name.
|
|
||||||
* @return The corresponding content type.
|
|
||||||
*/
|
|
||||||
private static String getContentType(String name) {
|
|
||||||
return switch (name.substring(name.lastIndexOf('.') + 1).toLowerCase()) {
|
|
||||||
case "html", "php" -> "text/html";
|
|
||||||
case "js" -> "text/javascript";
|
|
||||||
case "css" -> "text/css";
|
|
||||||
case "json" -> "application/json";
|
|
||||||
case "png" -> "image/png";
|
|
||||||
case "jpg", "jpeg" -> "image/jpeg";
|
|
||||||
case "mp4" -> "video/mp4";
|
|
||||||
case "mp3" -> "audio/mpeg3";
|
|
||||||
case "wav" -> "audio/wav";
|
|
||||||
case "pdf" -> "application/pdf";
|
|
||||||
default -> "text/plain";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a PHP file by executing it with the PHP interpreter and captures cookies.
|
|
||||||
*
|
|
||||||
* @param file The PHP file to render.
|
|
||||||
* @return A PHPResponse containing the output and cookies.
|
|
||||||
* @throws IOException If an I/O error occurs.
|
|
||||||
* @throws InterruptedException If the process is interrupted.
|
|
||||||
*/
|
|
||||||
private static PHPResponse renderPHPWithCookies(File file) throws IOException, InterruptedException {
|
|
||||||
// Execute the PHP file using the PHP interpreter
|
|
||||||
ProcessBuilder pb = new ProcessBuilder("php", file.getAbsolutePath());
|
|
||||||
pb.redirectErrorStream(true);
|
|
||||||
Process p = pb.start();
|
|
||||||
|
|
||||||
// Capture the output of the PHP process
|
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
||||||
InputStream processIn = p.getInputStream();
|
|
||||||
byte[] buf = new byte[8192];
|
|
||||||
int read;
|
|
||||||
while ((read = processIn.read(buf)) != -1) {
|
|
||||||
output.write(buf, 0, read);
|
|
||||||
}
|
|
||||||
p.waitFor();
|
|
||||||
|
|
||||||
// Parse the output to separate headers and body, and extract cookies
|
|
||||||
String fullOutput = output.toString(StandardCharsets.UTF_8);
|
|
||||||
Map<String, String> cookies = new HashMap<>();
|
|
||||||
|
|
||||||
// Split headers and body
|
|
||||||
String[] parts = fullOutput.split("\r\n\r\n", 2);
|
|
||||||
String body;
|
|
||||||
if (parts.length == 2) {
|
|
||||||
// Get headers and body
|
|
||||||
String headers = parts[0];
|
|
||||||
body = parts[1];
|
|
||||||
|
|
||||||
// Extract cookies from headers
|
|
||||||
for (String headerLine : headers.split("\r\n")) {
|
|
||||||
if (headerLine.toLowerCase().startsWith("set-cookie:")) {
|
|
||||||
String cookie = headerLine.substring("set-cookie:".length()).trim();
|
|
||||||
String[] kv = cookie.split(";", 2);
|
|
||||||
String[] pair = kv[0].split("=", 2);
|
|
||||||
if (pair.length == 2) cookies.put(pair[0], pair[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No headers, only body
|
|
||||||
body = fullOutput;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new PHPResponse(body, cookies);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the SSL socket for the web client and starts the receive thread.
|
|
||||||
*
|
|
||||||
* @param webSocket The SSL socket to set.
|
|
||||||
*/
|
|
||||||
public void setWebSocket(SSLSocket webSocket) {
|
|
||||||
if (webSocket != null) this.webSocket = webSocket;
|
|
||||||
this.receiveThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the web client is currently connected.
|
|
||||||
*
|
|
||||||
* @return True if connected, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean isConnected() {
|
|
||||||
return this.webSocket != null && this.webSocket.isConnected() && !this.webSocket.isClosed() && this.receiveThread.isAlive() && pipelineConnection.isConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnects the web client, closing streams and the socket.
|
|
||||||
*
|
|
||||||
* @return True if disconnection was successful, false if already disconnected.
|
|
||||||
*/
|
|
||||||
public synchronized boolean disconnect() {
|
|
||||||
if (!this.isConnected()) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
// Disconnect the underlying connection handler
|
|
||||||
pipelineConnection.disconnect();
|
|
||||||
|
|
||||||
// Interrupt the receive thread if it's still alive
|
|
||||||
if (this.receiveThread.isAlive()) {
|
|
||||||
this.receiveThread.interrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Close streams and the socket
|
|
||||||
this.outputStream.close();
|
|
||||||
this.inputStream.close();
|
|
||||||
this.webSocket.close();
|
|
||||||
} catch (IOException var2) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nullify references
|
|
||||||
this.webSocket = null;
|
|
||||||
this.outputStream = null;
|
|
||||||
this.inputStream = null;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the protocol version of the connected client.
|
|
||||||
*
|
|
||||||
* @return The protocol version of the client, defaults to PV_1_0_0_CLASSIC if not set.
|
|
||||||
*/
|
|
||||||
public ProtocolVersion getClientVersion() {
|
|
||||||
return clientVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : clientVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the protocol version of the connected client.
|
|
||||||
*
|
|
||||||
* @param clientVersion The protocol version to set.
|
|
||||||
*/
|
|
||||||
public void setClientVersion(ProtocolVersion clientVersion) {
|
|
||||||
if (!clientVersionLoaded) clientVersionLoaded = true;
|
|
||||||
if (clientVersion == null) this.clientVersion = clientVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the connected client is a stable client.
|
|
||||||
*
|
|
||||||
* @return True if the client is stable, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean isStableClient() {
|
|
||||||
// Check if the server version is stable
|
|
||||||
return !isBetaClient() && !isClassicClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the connected client supports stable protocol versions.
|
|
||||||
*
|
|
||||||
* @return True if the client supports stable versions, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean supportClientStable() {
|
|
||||||
boolean yes = false;
|
|
||||||
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
|
|
||||||
// Check if compatible version is stable
|
|
||||||
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE;
|
|
||||||
if (yes) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the client version is stable
|
|
||||||
return isStableClient() || yes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the connected client is a beta client.
|
|
||||||
*
|
|
||||||
* @return True if the client is beta, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean isBetaClient() {
|
|
||||||
// Check if the server version is beta
|
|
||||||
return getClientVersion().getProtocolType() == ProtocolVersion.ProtocolType.BETA;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the connected client supports beta protocol versions.
|
|
||||||
*
|
|
||||||
* @return True if the client supports beta versions, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean supportClientBeta() {
|
|
||||||
boolean yes = false;
|
|
||||||
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
|
|
||||||
// Check if compatible version is beta
|
|
||||||
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA;
|
|
||||||
if (yes) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the client version is beta
|
|
||||||
return isBetaClient() || yes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the connected client is a classic client.
|
|
||||||
*
|
|
||||||
* @return True if the client is classic, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean isClassicClient() {
|
|
||||||
return getClientVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the connected client supports classic protocol versions.
|
|
||||||
*
|
|
||||||
* @return True if the client supports classic versions, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean supportClientClassic() {
|
|
||||||
boolean yes = false;
|
|
||||||
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
|
|
||||||
// Check if compatible version is classic
|
|
||||||
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
|
||||||
if (yes) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the client version is classic
|
|
||||||
return isClassicClient() || yes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the connected client supports the protocol version of the given packet.
|
|
||||||
*
|
|
||||||
* @param packet The packet to check support for.
|
|
||||||
* @return True if the client supports the packet's protocol version, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean supportClientPacket(OACPacket packet) {
|
|
||||||
return supportClientVersion(packet.getProtocolVersion());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the connected client supports the given protocol version.
|
|
||||||
*
|
|
||||||
* @param targetVersion The protocol version to check support for.
|
|
||||||
* @return True if the client supports the target version, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean supportClientVersion(ProtocolVersion targetVersion) {
|
|
||||||
// Check if the client version matches the target version or is compatible
|
|
||||||
return getClientVersion() == targetVersion || getClientVersion().getCompatibleVersions().contains(targetVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the connected client supports the given protocol.
|
|
||||||
*
|
|
||||||
* @param protocol The protocol to check support for.
|
|
||||||
* @return True if the client supports the protocol, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean supportClientProtocol(ProtocolVersion.Protocol protocol) {
|
|
||||||
boolean yes = false;
|
|
||||||
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
|
|
||||||
// Check if compatible version supports the protocol
|
|
||||||
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
|
|
||||||
if (yes) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the client version supports the protocol
|
|
||||||
return getClientVersion().getSupportedProtocols().contains(protocol) || yes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receives and processes requests from the client.
|
|
||||||
* Handles authentication, file serving, and PHP rendering.
|
|
||||||
*/
|
|
||||||
private void receive() {
|
|
||||||
try {
|
|
||||||
while (this.isConnected()) {
|
|
||||||
Object received = this.inputStream.readObject();
|
|
||||||
|
|
||||||
try (InputStream in = webSocket.getInputStream(); OutputStream out = webSocket.getOutputStream()) {
|
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
|
|
||||||
String line;
|
|
||||||
String path = "/main.html";
|
|
||||||
Map<String, String> headers = new HashMap<>();
|
|
||||||
while ((line = reader.readLine()) != null && !line.isEmpty()) {
|
|
||||||
if (line.toLowerCase().startsWith("get") || line.toLowerCase().startsWith("post")) {
|
|
||||||
path = line.split(" ")[1];
|
|
||||||
}
|
|
||||||
if (line.contains(":")) {
|
|
||||||
String[] parts = line.split(":", 2);
|
|
||||||
headers.put(parts[0].trim().toLowerCase(), parts[1].trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
path = URLDecoder.decode(path, StandardCharsets.UTF_8);
|
|
||||||
path = normalizePath(path);
|
|
||||||
|
|
||||||
File file = new File(protocolWebServer.getProtocolBridge().getProtocolWebServer().getContentFolder(), path);
|
|
||||||
|
|
||||||
String sessionId = null;
|
|
||||||
if (headers.containsKey("cookie")) {
|
|
||||||
for (String cookie : headers.get("cookie").split(";")) {
|
|
||||||
cookie = cookie.trim();
|
|
||||||
if (cookie.startsWith("SESSIONID=")) {
|
|
||||||
sessionId = cookie.substring("SESSIONID=".length());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!file.exists() || !file.isFile()) {
|
|
||||||
sendResponse(out, 404, new File(protocolWebServer.getProtocolBridge().getProtocolWebServer().getErrorsFolder(), "404.html"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String clientIp = webSocket.getInetAddress().getHostAddress();
|
|
||||||
String userAgent = headers.getOrDefault("user-agent", null);
|
|
||||||
|
|
||||||
boolean loggedIn = sessionId != null && SessionManager.isValid(sessionId, clientIp, userAgent, protocolWebServer);
|
|
||||||
|
|
||||||
if (path.equals("/403-login") && headers.getOrDefault("content-type", "").startsWith("application/x-www-form-urlencoded")) {
|
|
||||||
Map<String, String> postParams = parsePostParams(in);
|
|
||||||
String login = postParams.get("login");
|
|
||||||
String password = postParams.get("password");
|
|
||||||
|
|
||||||
if (AuthManager.checkAuth(login, password)) {
|
|
||||||
String newSessionId = SessionManager.create(login, clientIp, userAgent, protocolWebServer);
|
|
||||||
Map<String, String> cookies = Map.of("Set-Cookie", "SESSIONID=" + newSessionId + "; HttpOnly; Path=/");
|
|
||||||
sendRedirect(out, "/main.html", cookies);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
sendRedirect(out, "/403.php", null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isMultipart(headers)) {
|
|
||||||
handleMultipart(in, headers, new File(protocolWebServer.getProtocolBridge().getProtocolWebServer().getContentFolder(), "uploads"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (RuleManager.requiresAuth(path) && !loggedIn) {
|
|
||||||
PHPResponse phpResp = renderPHPWithCookies(new File(protocolWebServer.getProtocolBridge().getProtocolWebServer().getContentFolder(), "403.php"));
|
|
||||||
sendResponse(out, 200, phpResp.body.getBytes(StandardCharsets.UTF_8), "text/html", phpResp.cookies);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (RuleManager.isDenied(path) && !RuleManager.isAllowed(path)) {
|
|
||||||
sendResponse(out, 403, new File(protocolWebServer.getProtocolBridge().getProtocolWebServer().getErrorsFolder(), "403.php"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.endsWith(".php")) {
|
|
||||||
PHPResponse phpResp = renderPHPWithCookies(file);
|
|
||||||
sendResponse(out, 200, phpResp.body.getBytes(StandardCharsets.UTF_8), "text/html", phpResp.cookies);
|
|
||||||
} else {
|
|
||||||
sendResponse(out, 200, Files.readAllBytes(file.toPath()), getContentType(path), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception var2) {
|
|
||||||
this.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set protocol bridge
|
|
||||||
*
|
|
||||||
* @param protocolWebServer The ProtocolWebServer object
|
|
||||||
*/
|
|
||||||
public void setProtocolWebServer(ProtocolWebServer protocolWebServer) {
|
|
||||||
if (this.protocolWebServer == null) this.protocolWebServer = protocolWebServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the response from a PHP script, including body and cookies.
|
|
||||||
*/
|
|
||||||
private static class PHPResponse {
|
|
||||||
String body;
|
|
||||||
Map<String, String> cookies;
|
|
||||||
|
|
||||||
public PHPResponse(String body, Map<String, String> cookies) {
|
|
||||||
this.body = body;
|
|
||||||
this.cookies = cookies;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,377 +1,256 @@
|
|||||||
package org.openautonomousconnection.protocol.side.web;
|
package org.openautonomousconnection.protocol.side.web;
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.file.ConfigurationManager;
|
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
|
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
|
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.string.RandomString;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||||
import org.openautonomousconnection.protocol.side.web.managers.AuthManager;
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
|
||||||
import org.openautonomousconnection.protocol.side.web.managers.RuleManager;
|
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.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.ProtocolWebServer_1_0_0_B;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketFlags;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebCompatMapper;
|
||||||
|
|
||||||
import javax.net.ssl.SSLServerSocket;
|
|
||||||
import javax.net.ssl.SSLSocket;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.io.IOException;
|
import java.util.Objects;
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the web server for the protocol.
|
* Protocol Web Server base for Web v1.0.1 (beta) with built-in v1.0.0 compatibility.
|
||||||
|
*
|
||||||
|
* <p>This class keeps the v1.0.0 entry point ({@link #onWebRequest(CustomConnectedClient, WebRequestPacket)})
|
||||||
|
* and adapts it to the v1.0.1 resource pipeline by mapping v1.0.0 requests to a synthetic
|
||||||
|
* {@link WebResourceRequestPacket} and mapping v1.0.1 responses back to {@link WebResponsePacket}.</p>
|
||||||
|
*
|
||||||
|
* <p>Important:
|
||||||
|
* <ul>
|
||||||
|
* <li>This class does NOT perform network packet dispatch automatically. Your server packet-receive listener
|
||||||
|
* must call the appropriate {@code handle*} methods (v1.0.1) or let the v1.0.0 pipeline call {@code onWebRequest}.</li>
|
||||||
|
* <li>Responses/streams must preserve correlation via {@link WebPacketHeader#getRequestId()} for v1.0.1 clients.</li>
|
||||||
|
* <li>v1.0.0 streaming packets cannot carry request correlation. If you stream, you must
|
||||||
|
* send 1.0.0 stream packets to 1.0.0 clients and v1.0.1 stream packets to v1.0.1 clients.</li>
|
||||||
|
* </ul>
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
||||||
public final class ProtocolWebServer {
|
public abstract class ProtocolWebServer extends ProtocolWebServer_1_0_0_B {
|
||||||
/**
|
|
||||||
* Folder for web content.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private final File contentFolder;
|
|
||||||
|
|
||||||
/**
|
private static final long LEGACY_TAB_ID = 1L;
|
||||||
* Folder for error pages.
|
private static final long LEGACY_PAGE_ID = 1L;
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private final File errorsFolder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure for server.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private final ServerCertificateFolderStructure folderStructure;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration manager for server settings.
|
|
||||||
*/
|
|
||||||
private final ConfigurationManager configurationManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Certificate files for SSL.
|
|
||||||
*/
|
|
||||||
private final File certFile;
|
|
||||||
/**
|
|
||||||
* Certificate files for SSL.
|
|
||||||
*/
|
|
||||||
private final File keyFile;
|
|
||||||
/**
|
|
||||||
* The configuration file for the web server.
|
|
||||||
*/
|
|
||||||
private final File configFile;
|
|
||||||
/**
|
|
||||||
* The reference to the ProtocolBridge Object
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private final ProtocolBridge protocolBridge;
|
|
||||||
/**
|
|
||||||
* The network server handling pipeline connections.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private NetworkServer pipelineServer;
|
|
||||||
/**
|
|
||||||
* The SSL server socket for web connections.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private SSLServerSocket webServer;
|
|
||||||
/**
|
|
||||||
* List of connected web clients.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private List<ConnectedWebClient> clients;
|
|
||||||
/**
|
|
||||||
* A unique secret for session management.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private String uniqueSessionString;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the web server with the given configuration, authentication, and rules files.
|
* Initializes the web server with the given configuration, authentication, and rules files.
|
||||||
*
|
*
|
||||||
* @param configFile The configuration file.
|
* @param authFile The authentication file.
|
||||||
* @param authFile The authentication file.
|
* @param rulesFile The rules 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.
|
* @throws Exception If an error occurs during initialization.
|
||||||
*/
|
*/
|
||||||
public ProtocolWebServer(File configFile, File authFile, File rulesFile, ProtocolBridge protocolBridge) throws Exception {
|
public ProtocolWebServer(File authFile, File rulesFile, int sessionExpire, int uploadSize) throws Exception {
|
||||||
this.protocolBridge = protocolBridge;
|
super(authFile, rulesFile, sessionExpire, uploadSize);
|
||||||
|
|
||||||
// Initialize the list of connected clients
|
|
||||||
this.clients = new ArrayList<>();
|
|
||||||
|
|
||||||
// Store the configuration file
|
|
||||||
this.configFile = configFile;
|
|
||||||
|
|
||||||
// Set up folder structure for certificates
|
|
||||||
folderStructure = new ServerCertificateFolderStructure();
|
|
||||||
|
|
||||||
// Check for necessary certificate files
|
|
||||||
checkFileExists(folderStructure.publicServerFolder, folderStructure.certPrefix, ".crt");
|
|
||||||
checkFileExists(folderStructure.privateServerFolder, folderStructure.certPrefix, ".key");
|
|
||||||
|
|
||||||
// Load configuration settings
|
|
||||||
this.configurationManager = getConfigurationManager(configFile);
|
|
||||||
|
|
||||||
// Set up content and error folders
|
|
||||||
contentFolder = new File("content");
|
|
||||||
errorsFolder = new File("errors");
|
|
||||||
|
|
||||||
// Create folders if they don't exist
|
|
||||||
if (!contentFolder.exists()) contentFolder.mkdir();
|
|
||||||
if (!errorsFolder.exists()) errorsFolder.mkdir();
|
|
||||||
|
|
||||||
// Set up certificate files based on public IP address
|
|
||||||
this.certFile = new File(folderStructure.publicServerFolder, folderStructure.certPrefix + NetworkUtils.getPublicIPAddress() + ".crt");
|
|
||||||
this.keyFile = new File(folderStructure.privateServerFolder, folderStructure.certPrefix + NetworkUtils.getPublicIPAddress() + ".key");
|
|
||||||
|
|
||||||
// Create auth and rules files with default content if they don't exist
|
|
||||||
if (!authFile.exists()) {
|
|
||||||
authFile.createNewFile();
|
|
||||||
FileUtils.writeFile(authFile, """
|
|
||||||
admin:5e884898da28047151d0e56f8dc6292773603d0d6aabbddab8f91d8e5f99f6c7
|
|
||||||
user:e99a18c428cb38d5f260853678922e03abd8335f
|
|
||||||
""");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create default rules file if it doesn't exist
|
|
||||||
if (!rulesFile.exists()) {
|
|
||||||
rulesFile.createNewFile();
|
|
||||||
FileUtils.writeFile(rulesFile, """
|
|
||||||
{
|
|
||||||
"allow": [
|
|
||||||
"index.html",
|
|
||||||
"css/*",
|
|
||||||
"private/info.php"
|
|
||||||
],
|
|
||||||
"deny": [
|
|
||||||
"private/*"
|
|
||||||
],
|
|
||||||
"auth": [
|
|
||||||
"private/*",
|
|
||||||
"admin/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
""");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load authentication and rules
|
|
||||||
uniqueSessionString = AuthManager.sha256(new RandomString(new Random(System.currentTimeMillis()).nextInt(10, 20)).nextString());
|
|
||||||
|
|
||||||
AuthManager.loadAuthFile(authFile);
|
|
||||||
RuleManager.loadRules(rulesFile);
|
|
||||||
|
|
||||||
// Initialize the pipeline server
|
|
||||||
pipelineServer = new NetworkServer.ServerBuilder().
|
|
||||||
setPort(configurationManager.getInt("port.pipeline")).setTimeout(0).
|
|
||||||
setPacketHandler(protocolBridge.getProtocolSettings().packetHandler).setEventManager(protocolBridge.getProtocolSettings().eventManager).
|
|
||||||
setLogger(protocolBridge.getLogger()).
|
|
||||||
setServerCertificate(certFile, keyFile).setRootCAFolder(folderStructure.publicCAFolder).
|
|
||||||
build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
* Retrieves a connected web client by its client ID.
|
public final void onPacketRead(C_PacketReadEvent event) {
|
||||||
*
|
Objects.requireNonNull(event, "event");
|
||||||
* @param clientID The client ID to search for.
|
|
||||||
* @return The connected web client with the specified ID, or null if not found.
|
|
||||||
*/
|
|
||||||
public ConnectedWebClient getClientByID(int clientID) {
|
|
||||||
for (ConnectedWebClient client : clients)
|
|
||||||
if (client.getPipelineConnection().getClientID() == clientID) return client;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
Object packet = event.getPacket();
|
||||||
* Starts the web server to accept and handle client connections.
|
if (packet == null) return;
|
||||||
*
|
|
||||||
* @throws Exception If an error occurs while starting the server.
|
|
||||||
*/
|
|
||||||
public void startWebServer() throws Exception {
|
|
||||||
// Start the pipeline server
|
|
||||||
pipelineServer.start();
|
|
||||||
|
|
||||||
// Create the SSL server socket for web connections
|
CustomConnectedClient client = getClientByID(event.getClient().getUniqueID());
|
||||||
webServer = (SSLServerSocket) NetworkServer.ServerBuilder.
|
if (client == null) return;
|
||||||
createSSLServerSocketFactory(folderStructure.publicCAFolder, certFile, keyFile).
|
|
||||||
createServerSocket(configurationManager.getInt("port"));
|
|
||||||
webServer.setSoTimeout(0);
|
|
||||||
webServer.setEnabledProtocols(pipelineServer.getServerSocket().getEnabledProtocols());
|
|
||||||
|
|
||||||
// Stop WebServer if pipelineServer dies
|
try {
|
||||||
new Thread(() -> {
|
if (packet instanceof WebNavigateRequestPacket nav) {
|
||||||
while (true) {
|
WebNavigateAckPacket ack = handleNavigate(client, nav);
|
||||||
// Check if the pipeline server is still running
|
client.getConnection().sendPacket(ack, TransportProtocol.TCP);
|
||||||
if (pipelineServer == null || !pipelineServer.getServerSocket().isBound()) {
|
return;
|
||||||
try {
|
|
||||||
// Stop the web server
|
|
||||||
onPipelineStop();
|
|
||||||
} catch (IOException e) {
|
|
||||||
pipelineServer.getLogger().exception("Failed to stop WebServer", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Sleep for a while before checking again
|
|
||||||
Thread.sleep(1000);
|
|
||||||
} catch (InterruptedException ignored) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}).start();
|
|
||||||
|
|
||||||
// Handle every client
|
if (packet instanceof WebResourceRequestPacket req) {
|
||||||
new Thread(() -> {
|
WebResourceResponsePacket resp = handleResource(client, req);
|
||||||
while (true) {
|
client.getConnection().sendPacket(resp, TransportProtocol.TCP);
|
||||||
try {
|
return;
|
||||||
// Accept incoming client connections
|
|
||||||
SSLSocket client = (SSLSocket) webServer.accept();
|
|
||||||
|
|
||||||
for (ConnectedWebClient connectedWebClient : clients) {
|
|
||||||
if (connectedWebClient.getPipelineConnection().getClientID() != -1 && connectedWebClient.isClientVersionLoaded()) {
|
|
||||||
// Assign socket to an existing connected client
|
|
||||||
connectedWebClient.setWebSocket(client);
|
|
||||||
connectedWebClient.setProtocolWebServer(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
pipelineServer.getLogger().exception("Failed to accept WebClient", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}).start();
|
|
||||||
|
if (packet instanceof WebDocumentApplyRequestPacket apply) {
|
||||||
|
WebDocumentApplyResponsePacket resp = handleDocumentApply(client, apply);
|
||||||
|
client.getConnection().sendPacket(resp, TransportProtocol.TCP);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
getProtocolBridge().getProtocolValues().logger.exception("Failed to handle packet", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the shutdown of the web server when the pipeline server stops.
|
* Server-side dispatcher entry point for a navigation request (packet id 01).
|
||||||
*
|
*
|
||||||
* @throws IOException If an I/O error occurs while closing the web server.
|
* @param client connected client
|
||||||
|
* @param request navigation request
|
||||||
|
* @return navigation ack to send back
|
||||||
*/
|
*/
|
||||||
private void onPipelineStop() throws IOException {
|
public final WebNavigateAckPacket handleNavigate(CustomConnectedClient client, WebNavigateRequestPacket request) {
|
||||||
webServer.close();
|
Objects.requireNonNull(client, "client");
|
||||||
|
Objects.requireNonNull(request, "request");
|
||||||
|
|
||||||
|
WebNavigateAckPacket ack = onNavigateRequest(client, request);
|
||||||
|
if (ack == null) {
|
||||||
|
ack = new WebNavigateAckPacket(
|
||||||
|
mirrorHeader(request.getHeader(), WebPacketFlags.NAVIGATION),
|
||||||
|
false,
|
||||||
|
"onNavigateRequest returned null"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ack;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the required certificate files exist in the specified folder.
|
* Server-side dispatcher entry point for a resource request (packet id 03).
|
||||||
*
|
*
|
||||||
* @param folder The folder to check for certificate files.
|
* <p>Streaming:
|
||||||
* @param prefix The prefix of the certificate files.
|
* <ul>
|
||||||
* @param extension The extension of the certificate files.
|
* <li>If you want to stream the body, return a {@link WebResourceResponsePacket} with an empty body,
|
||||||
* @throws CertificateException If a required certificate file is missing or invalid.
|
* and then send {@link WebStreamStartPacket_v1_0_1_B}/{@link WebStreamChunkPacket_v1_0_1_B}/{@link WebStreamEndPacket_v1_0_1_B}
|
||||||
* @throws IOException If an I/O error occurs while checking the files.
|
* using the SAME {@code requestId}.</li>
|
||||||
*/
|
* </ul>
|
||||||
private void checkFileExists(File folder, String prefix, String extension) throws CertificateException, IOException {
|
* </p>
|
||||||
boolean found = false;
|
|
||||||
|
|
||||||
// Ensure the folder exists
|
|
||||||
if (folder == null) throw new FileNotFoundException("Folder does not exist");
|
|
||||||
|
|
||||||
// List all files in the folder
|
|
||||||
File[] files = folder.listFiles();
|
|
||||||
if (files == null || files.length == 0)
|
|
||||||
throw new FileNotFoundException("Folder " + folder.getAbsolutePath() + " is empty");
|
|
||||||
|
|
||||||
// Check for the required certificate file
|
|
||||||
for (File file : files) {
|
|
||||||
if (!file.getName().startsWith(prefix))
|
|
||||||
throw new CertificateException(file.getAbsolutePath() + " is not valid");
|
|
||||||
|
|
||||||
// Check for file matching the public IP address
|
|
||||||
if (!found) found = file.getName().equalsIgnoreCase(prefix + NetworkUtils.getPublicIPAddress() + extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throw exception if the required file is not found
|
|
||||||
if (!found) throw new CertificateException("Missing " + prefix + NetworkUtils.getPublicIPAddress() + extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the configuration manager for the web server.
|
|
||||||
*
|
*
|
||||||
* @return The configuration manager.
|
* @param client connected client
|
||||||
* @throws IOException If an I/O error occurs while loading or saving the configuration.
|
* @param request resource request
|
||||||
|
* @return resource response packet to send back
|
||||||
*/
|
*/
|
||||||
public ConfigurationManager getConfigurationManager() throws IOException {
|
public final WebResourceResponsePacket handleResource(CustomConnectedClient client, WebResourceRequestPacket request) {
|
||||||
return getConfigurationManager(configFile);
|
Objects.requireNonNull(client, "client");
|
||||||
|
Objects.requireNonNull(request, "request");
|
||||||
|
|
||||||
|
WebResourceResponsePacket resp = onResourceRequest(client, request);
|
||||||
|
if (resp == null) {
|
||||||
|
resp = new WebResourceResponsePacket(
|
||||||
|
mirrorHeader(request.getHeader(), WebPacketFlags.RESOURCE),
|
||||||
|
500,
|
||||||
|
"text/plain; charset=utf-8",
|
||||||
|
java.util.Collections.emptyMap(),
|
||||||
|
"onResourceRequest returned null".getBytes(StandardCharsets.UTF_8),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads and initializes the configuration manager with default settings if necessary.
|
* Server-side dispatcher entry point for a document apply request (packet id 20).
|
||||||
*
|
*
|
||||||
* @param configFile The configuration file to load.
|
* @param client connected client
|
||||||
* @return The initialized configuration manager.
|
* @param request apply request
|
||||||
* @throws IOException If an I/O error occurs while loading or saving the configuration.
|
* @return apply response
|
||||||
*/
|
*/
|
||||||
private ConfigurationManager getConfigurationManager(File configFile) throws IOException {
|
public final WebDocumentApplyResponsePacket handleDocumentApply(CustomConnectedClient client, WebDocumentApplyRequestPacket request) {
|
||||||
if (!configFile.exists()) configFile.createNewFile();
|
Objects.requireNonNull(client, "client");
|
||||||
|
Objects.requireNonNull(request, "request");
|
||||||
|
|
||||||
ConfigurationManager configurationManager = new ConfigurationManager(configFile);
|
WebDocumentApplyResponsePacket resp = onDocumentApplyRequest(client, request);
|
||||||
configurationManager.loadProperties();
|
if (resp == null) {
|
||||||
|
resp = new WebDocumentApplyResponsePacket(
|
||||||
if (!configurationManager.isSet("port.webserver")) {
|
mirrorHeader(request.getHeader(), 0),
|
||||||
configurationManager.set("port.webserver", 9824);
|
false,
|
||||||
configurationManager.saveProperties();
|
"onDocumentApplyRequest returned null"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
return resp;
|
||||||
if (!configurationManager.isSet("port.pipeline")) {
|
|
||||||
configurationManager.set("port.pipeline", 9389);
|
|
||||||
configurationManager.saveProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!configurationManager.isSet("filemaxuploadmb")) {
|
|
||||||
configurationManager.set("filemaxuploadmb", 1000);
|
|
||||||
configurationManager.saveProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!configurationManager.isSet("sessionexpireminutes")) {
|
|
||||||
configurationManager.set("sessionexpireminutes", 60);
|
|
||||||
configurationManager.saveProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
return configurationManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the folder structure for server certificates.
|
* Hook: called when the server receives a navigation request.
|
||||||
|
*
|
||||||
|
* @param client connected client
|
||||||
|
* @param request navigation request
|
||||||
|
* @return ack packet (must include mirrored requestId/tabId/pageId/frameId)
|
||||||
*/
|
*/
|
||||||
public final class ServerCertificateFolderStructure {
|
protected abstract WebNavigateAckPacket onNavigateRequest(CustomConnectedClient client, WebNavigateRequestPacket request);
|
||||||
public final File certificatesFolder;
|
|
||||||
|
|
||||||
public final File publicFolder;
|
/**
|
||||||
public final File privateFolder;
|
* Hook: called when the server receives a resource request.
|
||||||
|
*
|
||||||
|
* @param client connected client
|
||||||
|
* @param request resource request
|
||||||
|
* @return response packet (must include mirrored requestId/tabId/pageId/frameId)
|
||||||
|
*/
|
||||||
|
protected abstract WebResourceResponsePacket onResourceRequest(CustomConnectedClient client, WebResourceRequestPacket request);
|
||||||
|
|
||||||
public final File privateCAFolder;
|
/**
|
||||||
public final File privateServerFolder;
|
* Hook: called when the server receives a document apply request.
|
||||||
|
*
|
||||||
|
* @param client connected client
|
||||||
|
* @param request apply request
|
||||||
|
* @return apply response packet (must include mirrored requestId/tabId/pageId/frameId)
|
||||||
|
*/
|
||||||
|
protected abstract WebDocumentApplyResponsePacket onDocumentApplyRequest(CustomConnectedClient client, WebDocumentApplyRequestPacket request);
|
||||||
|
|
||||||
public final File publicCAFolder;
|
/**
|
||||||
public final File publicServerFolder;
|
* v1.0.0 entry point.
|
||||||
public final String caPrefix = "ca_server_";
|
*
|
||||||
public final String certPrefix = "cert_server_";
|
* <p>This method is invoked by the v1.0.0 server pipeline when a 1.0.0 client sends a 1.0.0 request packet.
|
||||||
|
* We adapt it into the v1.0.1 resource pipeline.</p>
|
||||||
|
*
|
||||||
|
* @param client connected client
|
||||||
|
* @param request 1.0.0 request packet
|
||||||
|
* @return 1.0.0 response packet
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
|
||||||
|
@Override
|
||||||
|
public WebResponsePacket onWebRequest(CustomConnectedClient client, WebRequestPacket request) {
|
||||||
|
Objects.requireNonNull(client, "client");
|
||||||
|
Objects.requireNonNull(request, "request");
|
||||||
|
|
||||||
public ServerCertificateFolderStructure() {
|
// 1.0.0 requests have no correlation fields. Create synthetic ones.
|
||||||
certificatesFolder = new File("certificates");
|
long requestId = WebCompatMapper.nextCompatRequestId();
|
||||||
|
long tabId = LEGACY_TAB_ID;
|
||||||
|
long pageId = LEGACY_PAGE_ID;
|
||||||
|
|
||||||
publicFolder = new File(certificatesFolder, "public");
|
WebResourceRequestPacket mapped = WebCompatMapper.toResourceRequest(requestId, tabId, pageId, request, getProtocolBridge());
|
||||||
privateFolder = new File(certificatesFolder, "private");
|
WebResourceResponsePacket resp = onResourceRequest(client, mapped);
|
||||||
|
|
||||||
privateCAFolder = new File(privateFolder, "ca");
|
if (resp == null) {
|
||||||
privateServerFolder = new File(privateFolder, "server");
|
return new WebResponsePacket(
|
||||||
|
500,
|
||||||
publicCAFolder = new File(publicFolder, "ca");
|
"text/plain; charset=utf-8",
|
||||||
publicServerFolder = new File(publicFolder, "server");
|
java.util.Collections.emptyMap(),
|
||||||
|
"onResourceRequest returned null".getBytes(StandardCharsets.UTF_8)
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,24 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.side.web.events;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
|
||||||
import org.openautonomousconnection.protocol.side.web.ConnectedWebClient;
|
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event triggered when a web client connects to the web server.
|
|
||||||
*/
|
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
|
||||||
public final class ConnectedWebClientEvent extends Event {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The connected web client.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private final ConnectedWebClient webClient;
|
|
||||||
|
|
||||||
public ConnectedWebClientEvent(ConnectedWebClient webClient) {
|
|
||||||
this.webClient = webClient;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,95 +6,168 @@ import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
|||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages access rules for web resources.
|
* Manages access rules for web resources.
|
||||||
* Loads allow, deny, and auth rules from a JSON file and provides methods to check access.
|
*
|
||||||
|
* <p>Rules are loaded from a JSON file with the structure:
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "allow": ["index.html", "css/*"],
|
||||||
|
* "deny": ["private/*"],
|
||||||
|
* "auth": ["private/*", "admin/*"]
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>Matching is performed against normalized web paths (forward slashes).
|
||||||
|
* Patterns are treated as glob patterns where '*' matches any sequence.</p>
|
||||||
*/
|
*/
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
||||||
public final class RuleManager {
|
public final class RuleManager {
|
||||||
/**
|
|
||||||
* Lists of path patterns for allow, deny, and auth rules
|
private static List<Pattern> allow = List.of();
|
||||||
*/
|
private static List<Pattern> deny = List.of();
|
||||||
private static List<String> allow;
|
private static List<Pattern> auth = List.of();
|
||||||
|
|
||||||
|
private RuleManager() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists of path patterns for allow, deny, and auth rules
|
* Loads rules from a JSON file and compiles patterns.
|
||||||
*/
|
|
||||||
private static List<String> deny;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lists of path patterns for allow, deny, and auth rules
|
|
||||||
*/
|
|
||||||
private static List<String> auth;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads rules from a JSON file.
|
|
||||||
* The JSON should have the structure:
|
|
||||||
* {
|
|
||||||
* "allow": ["pattern1", "pattern2", ...],
|
|
||||||
* "deny": ["pattern1", "pattern2", ...],
|
|
||||||
* "auth": ["pattern1", "pattern2", ...]
|
|
||||||
* }
|
|
||||||
*
|
*
|
||||||
* @param rulesFile The JSON file containing the rules.
|
* @param rulesFile JSON rules file
|
||||||
* @throws Exception If an error occurs reading the file or parsing JSON.
|
* @throws Exception if reading/parsing fails
|
||||||
*/
|
*/
|
||||||
public static void loadRules(File rulesFile) throws Exception {
|
public static void loadRules(File rulesFile) throws Exception {
|
||||||
// Load and parse the JSON file
|
String json = Files.readString(rulesFile.toPath(), StandardCharsets.UTF_8);
|
||||||
String json = new String(Files.readAllBytes(rulesFile.toPath()));
|
|
||||||
Map<String, List<String>> map = new Gson().fromJson(json, new TypeToken<Map<String, List<String>>>() {
|
|
||||||
}.getType());
|
|
||||||
|
|
||||||
// Default to empty lists if keys are missing
|
Map<String, List<String>> map = new Gson().fromJson(
|
||||||
allow = map.getOrDefault("allow", List.of());
|
json,
|
||||||
deny = map.getOrDefault("deny", List.of());
|
new TypeToken<Map<String, List<String>>>() {
|
||||||
auth = map.getOrDefault("auth", List.of());
|
}.getType()
|
||||||
|
);
|
||||||
|
|
||||||
|
allow = compileList(map.getOrDefault("allow", List.of()));
|
||||||
|
deny = compileList(map.getOrDefault("deny", List.of()));
|
||||||
|
auth = compileList(map.getOrDefault("auth", List.of()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the given path is allowed based on the allow rules.
|
* Returns true if the path is allowed by allow rules.
|
||||||
*
|
*
|
||||||
* @param path The path to check.
|
* <p>Important: If allow list is empty, everything is allowed (default-open).</p>
|
||||||
* @return True if the path is allowed, false otherwise.
|
*
|
||||||
|
* @param path web path (e.g. "/index.html" or "index.html")
|
||||||
|
* @return true if allowed
|
||||||
*/
|
*/
|
||||||
public static boolean isAllowed(String path) {
|
public static boolean isAllowed(String path) {
|
||||||
return allow.stream().anyMatch(p -> pathMatches(path, p));
|
String p = normalizePath(path);
|
||||||
|
|
||||||
|
// Default-open behavior if allow list is empty
|
||||||
|
if (allow.isEmpty()) return true;
|
||||||
|
|
||||||
|
return matchesAny(p, allow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the given path is denied based on the deny rules.
|
* Returns true if the path is denied.
|
||||||
*
|
*
|
||||||
* @param path The path to check.
|
* @param path web path
|
||||||
* @return True if the path is denied, false otherwise.
|
* @return true if denied
|
||||||
*/
|
*/
|
||||||
public static boolean isDenied(String path) {
|
public static boolean isDenied(String path) {
|
||||||
return deny.stream().anyMatch(p -> pathMatches(path, p));
|
String p = normalizePath(path);
|
||||||
|
return matchesAny(p, deny);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the given path requires authentication based on the auth rules.
|
* Returns true if the path requires authentication.
|
||||||
*
|
*
|
||||||
* @param path The path to check.
|
* @param path web path
|
||||||
* @return True if the path requires authentication, false otherwise.
|
* @return true if auth required
|
||||||
*/
|
*/
|
||||||
public static boolean requiresAuth(String path) {
|
public static boolean requiresAuth(String path) {
|
||||||
return auth.stream().anyMatch(p -> pathMatches(path, p));
|
String p = normalizePath(path);
|
||||||
|
return matchesAny(p, auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean matchesAny(String normalizedPath, List<Pattern> patterns) {
|
||||||
|
for (Pattern pat : patterns) {
|
||||||
|
if (pat.matcher(normalizedPath).matches()) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Pattern> compileList(List<String> globs) {
|
||||||
|
if (globs == null || globs.isEmpty()) return List.of();
|
||||||
|
return globs.stream()
|
||||||
|
.map(RuleManager::compileGlob)
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to check if a path matches a pattern.
|
* Compiles a glob pattern to a full-match regex Pattern.
|
||||||
* Patterns can include '*' as a wildcard.
|
|
||||||
*
|
*
|
||||||
* @param path The path to check.
|
* <p>Rules:
|
||||||
* @param pattern The pattern to match against.
|
* <ul>
|
||||||
* @return True if the path matches the pattern, false otherwise.
|
* <li>'*' matches any sequence of characters (including '/')</li>
|
||||||
|
* <li>All other regex meta chars are escaped</li>
|
||||||
|
* <li>Matching is performed against the entire normalized path</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param glob glob pattern, e.g. "css/*" or "private/*"
|
||||||
|
* @return compiled Pattern
|
||||||
*/
|
*/
|
||||||
private static boolean pathMatches(String path, String pattern) {
|
private static Pattern compileGlob(String glob) {
|
||||||
pattern = pattern.replace("/", File.separator).replace("*", ".*");
|
String g = normalizePath(glob);
|
||||||
return path.matches(pattern);
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -128,7 +128,7 @@ public final class SessionManager {
|
|||||||
this.login = login;
|
this.login = login;
|
||||||
this.ip = ip;
|
this.ip = ip;
|
||||||
this.userAgent = userAgent;
|
this.userAgent = userAgent;
|
||||||
this.expiresAt = System.currentTimeMillis() + (long) protocolWebServer.getConfigurationManager().getInt("sessionexpireminutes") * 60 * 1000;
|
this.expiresAt = System.currentTimeMillis() + (long) protocolWebServer.getSessionExpire() * 60 * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -158,7 +158,7 @@ public final class SessionManager {
|
|||||||
* @throws IOException If an I/O error occurs.
|
* @throws IOException If an I/O error occurs.
|
||||||
*/
|
*/
|
||||||
void refresh(ProtocolWebServer protocolWebServer) throws IOException {
|
void refresh(ProtocolWebServer protocolWebServer) throws IOException {
|
||||||
this.expiresAt = System.currentTimeMillis() + (long) protocolWebServer.getConfigurationManager().getInt("sessionexpireminutes") * 60 * 1000;
|
this.expiresAt = System.currentTimeMillis() + (long) protocolWebServer.getSessionExpire() * 60 * 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta;
|
||||||
|
/**
|
||||||
|
* Installs the "web://" protocol handler using java.protocol.handler.pkgs.
|
||||||
|
* Recommended to use Version v1.0.0-BETA or newer
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.3")
|
||||||
|
public abstract class LibClientImpl_v1_0_0_B {
|
||||||
|
/**
|
||||||
|
* Called when connecting to the resolved server endpoint fails.
|
||||||
|
*
|
||||||
|
* @param exception the connection failure
|
||||||
|
*/
|
||||||
|
public abstract void serverConnectionFailed(Exception exception);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the current session token across multiple HttpURLConnection instances.
|
||||||
|
*
|
||||||
|
* <p>JavaFX WebView creates a new connection per navigation, so headers must be re-injected
|
||||||
|
* for every request.</p>
|
||||||
|
*/
|
||||||
|
public final class OacSessionJar {
|
||||||
|
|
||||||
|
private volatile String session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a session token (e.g. from response header "session").
|
||||||
|
*
|
||||||
|
* @param session session token
|
||||||
|
*/
|
||||||
|
public void store(String session) {
|
||||||
|
String s = (session == null) ? null : session.trim();
|
||||||
|
this.session = (s == null || s.isEmpty()) ? null : s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return stored session token or null
|
||||||
|
*/
|
||||||
|
public String get() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta;
|
||||||
|
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.ProtocolException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpURLConnection implementation that maps "web://" URLs to OAC WebRequestPacket/WebResponsePacket.
|
||||||
|
*
|
||||||
|
* <p>Important semantics for JavaFX WebView:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>WebView may call {@link #connect()} before {@link #getOutputStream()}.</li>
|
||||||
|
* <li>WebView creates a NEW connection instance per navigation; therefore persistent headers
|
||||||
|
* (like session) must be injected from an external store per request.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class OacWebHttpURLConnection extends HttpURLConnection {
|
||||||
|
|
||||||
|
private static final int MAX_REDIRECTS = 8;
|
||||||
|
|
||||||
|
private final OacWebRequestBroker broker;
|
||||||
|
private final OacSessionJar sessionJar;
|
||||||
|
|
||||||
|
private final Map<String, String> requestHeaders = new LinkedHashMap<>();
|
||||||
|
private final ByteArrayOutputStream requestBody = new ByteArrayOutputStream(1024);
|
||||||
|
|
||||||
|
private boolean connected;
|
||||||
|
private boolean requestSent;
|
||||||
|
|
||||||
|
private OacWebResponse response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new OAC HttpURLConnection.
|
||||||
|
*
|
||||||
|
* @param url the web:// URL
|
||||||
|
* @param broker request broker
|
||||||
|
* @param sessionJar shared session store (must be shared across connections)
|
||||||
|
*/
|
||||||
|
public OacWebHttpURLConnection(URL url, OacWebRequestBroker broker, OacSessionJar sessionJar) {
|
||||||
|
super(url);
|
||||||
|
this.broker = Objects.requireNonNull(broker, "broker");
|
||||||
|
this.sessionJar = Objects.requireNonNull(sessionJar, "sessionJar");
|
||||||
|
this.method = "GET";
|
||||||
|
setInstanceFollowRedirects(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String headerValue(Map<String, String> headers, String nameLower) {
|
||||||
|
if (headers == null || headers.isEmpty() || nameLower == null) return null;
|
||||||
|
String needle = nameLower.trim().toLowerCase(Locale.ROOT);
|
||||||
|
for (Map.Entry<String, String> e : headers.entrySet()) {
|
||||||
|
if (e.getKey() == null) continue;
|
||||||
|
if (e.getKey().trim().toLowerCase(Locale.ROOT).equals(needle)) {
|
||||||
|
return e.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequestProperty(String key, String value) {
|
||||||
|
if (key == null) return;
|
||||||
|
// DO NOT block after connect(): WebView may call connect() early.
|
||||||
|
// Only block once the request was actually sent.
|
||||||
|
if (requestSent) throw new IllegalStateException("Request already sent");
|
||||||
|
if (value == null) requestHeaders.remove(key);
|
||||||
|
else requestHeaders.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRequestProperty(String key) {
|
||||||
|
if (key == null) return null;
|
||||||
|
for (Map.Entry<String, String> e : requestHeaders.entrySet()) {
|
||||||
|
if (e.getKey() != null && e.getKey().equalsIgnoreCase(key)) return e.getValue();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getRequestProperties() {
|
||||||
|
Map<String, List<String>> out = new LinkedHashMap<>();
|
||||||
|
for (Map.Entry<String, String> e : requestHeaders.entrySet()) {
|
||||||
|
out.put(e.getKey(), e.getValue() == null ? List.of() : List.of(e.getValue()));
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableMap(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequestMethod(String method) throws ProtocolException {
|
||||||
|
if (method == null) throw new ProtocolException("method is null");
|
||||||
|
if (requestSent) throw new ProtocolException("Request already sent");
|
||||||
|
|
||||||
|
String m = method.trim().toUpperCase(Locale.ROOT);
|
||||||
|
if (!m.equals("GET") && !m.equals("POST")) {
|
||||||
|
throw new ProtocolException("Unsupported method: " + method);
|
||||||
|
}
|
||||||
|
this.method = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream getOutputStream() throws IOException {
|
||||||
|
// WebView may call connect() first, so do NOT throw "Already connected".
|
||||||
|
if (requestSent) throw new IllegalStateException("Request already sent");
|
||||||
|
|
||||||
|
setDoOutput(true);
|
||||||
|
return requestBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect() {
|
||||||
|
// MUST NOT send here. WebView may call connect() before writing the POST body.
|
||||||
|
connected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureResponse() throws IOException {
|
||||||
|
if (requestSent) return;
|
||||||
|
|
||||||
|
URL cur = this.url;
|
||||||
|
|
||||||
|
String methodStr = (this.method == null) ? "GET" : this.method.trim().toUpperCase(Locale.ROOT);
|
||||||
|
WebRequestMethod reqMethod = "POST".equals(methodStr) ? WebRequestMethod.POST : WebRequestMethod.GET;
|
||||||
|
|
||||||
|
// Snapshot headers/body at send time.
|
||||||
|
Map<String, String> carryHeaders = new LinkedHashMap<>(requestHeaders);
|
||||||
|
|
||||||
|
// Each navigation creates a new connection, so we re-add the session for every request.
|
||||||
|
String session = sessionJar.get();
|
||||||
|
if (session != null && !session.isBlank() && headerValue(carryHeaders, "session") == null) {
|
||||||
|
carryHeaders.put("session", session);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] carryBody = null;
|
||||||
|
if (getDoOutput()) {
|
||||||
|
carryBody = requestBody.toByteArray();
|
||||||
|
if (reqMethod == WebRequestMethod.POST && carryBody == null) carryBody = new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reqMethod == WebRequestMethod.POST && headerValue(carryHeaders, "content-type") == null) {
|
||||||
|
carryHeaders.put("content-type", "application/x-www-form-urlencoded; charset=utf-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
OacWebResponse resp = null;
|
||||||
|
WebRequestMethod carryMethod = reqMethod;
|
||||||
|
|
||||||
|
for (int i = 0; i <= MAX_REDIRECTS; i++) {
|
||||||
|
resp = broker.fetch(cur, carryMethod, carryHeaders, carryBody);
|
||||||
|
|
||||||
|
String newSession = headerValue(resp.headers(), "session");
|
||||||
|
if (newSession != null && !newSession.isBlank()) {
|
||||||
|
sessionJar.store(newSession);
|
||||||
|
// keep it for the next request in this redirect chain too
|
||||||
|
carryHeaders.put("session", newSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
int code = resp.statusCode();
|
||||||
|
if (!getInstanceFollowRedirects()) break;
|
||||||
|
|
||||||
|
if (code == 301 || code == 302 || code == 303 || code == 307 || code == 308) {
|
||||||
|
String loc = headerValue(resp.headers(), "location");
|
||||||
|
if (loc == null || loc.isBlank()) break;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cur = new URL(cur, loc);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code == 303) {
|
||||||
|
carryMethod = WebRequestMethod.GET;
|
||||||
|
carryBody = null;
|
||||||
|
} else if ((code == 301 || code == 302) && carryMethod == WebRequestMethod.POST) {
|
||||||
|
carryMethod = WebRequestMethod.GET;
|
||||||
|
carryBody = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.response = resp;
|
||||||
|
this.requestSent = true;
|
||||||
|
this.connected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() throws IOException {
|
||||||
|
ensureResponse();
|
||||||
|
if (response == null) return new ByteArrayInputStream(new byte[0]);
|
||||||
|
return response.bodyStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getErrorStream() {
|
||||||
|
try {
|
||||||
|
ensureResponse();
|
||||||
|
if (response == null) return null;
|
||||||
|
int code = response.statusCode();
|
||||||
|
return (code >= 400) ? response.bodyStream() : null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getResponseCode() throws IOException {
|
||||||
|
ensureResponse();
|
||||||
|
return response == null ? -1 : response.statusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContentType() {
|
||||||
|
try {
|
||||||
|
ensureResponse();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return "application/octet-stream";
|
||||||
|
}
|
||||||
|
String ct = (response == null) ? null : response.contentType();
|
||||||
|
return (ct == null || ct.isBlank()) ? "application/octet-stream" : ct;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getContentLength() {
|
||||||
|
try {
|
||||||
|
ensureResponse();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
long len = (response == null) ? -1L : response.contentLength();
|
||||||
|
return (len <= 0 || len > Integer.MAX_VALUE) ? -1 : (int) len;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getContentLengthLong() {
|
||||||
|
try {
|
||||||
|
ensureResponse();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return -1L;
|
||||||
|
}
|
||||||
|
return (response == null) ? -1L : response.contentLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getHeaderFields() {
|
||||||
|
try {
|
||||||
|
ensureResponse();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
if (response == null) return Map.of();
|
||||||
|
|
||||||
|
Map<String, List<String>> out = new LinkedHashMap<>();
|
||||||
|
for (Map.Entry<String, String> e : response.headers().entrySet()) {
|
||||||
|
String k = e.getKey();
|
||||||
|
String v = e.getValue();
|
||||||
|
if (k == null) continue;
|
||||||
|
out.put(k, v == null ? List.of() : List.of(v));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHeaderField(String name) {
|
||||||
|
if (name == null) return null;
|
||||||
|
try {
|
||||||
|
ensureResponse();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (response == null) return null;
|
||||||
|
return headerValue(response.headers(), name.trim().toLowerCase(Locale.ROOT));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnect() {
|
||||||
|
// No persistent socket owned by this object.
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean usingProxy() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamChunkPacket_v1_0_0_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamEndPacket_v1_0_0_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamStartPacket_v1_0_0_B;
|
||||||
|
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
|
||||||
|
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolServerEvent;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives incoming OAC web response packets and forwards them into the request broker.
|
||||||
|
*
|
||||||
|
* <p>Important:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>The shown protocol types do not contain any correlation id.</li>
|
||||||
|
* <li>Therefore, the broker must treat the connection as single-flight (one in-flight request).</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class OacWebPacketListener extends EventListener {
|
||||||
|
|
||||||
|
private final OacWebRequestBroker broker;
|
||||||
|
private final ProtocolClient client;
|
||||||
|
private final LibClientImpl_v1_0_0_B impl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new listener bound to the given broker.
|
||||||
|
*
|
||||||
|
* @param broker broker instance
|
||||||
|
* @param client protocol client
|
||||||
|
*/
|
||||||
|
public OacWebPacketListener(OacWebRequestBroker broker, ProtocolClient client, LibClientImpl_v1_0_0_B impl) {
|
||||||
|
this.broker = Objects.requireNonNull(broker, "broker");
|
||||||
|
this.client = Objects.requireNonNull(client, "client");
|
||||||
|
this.impl = Objects.requireNonNull(impl, "impl");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the broker that the server connection is established.
|
||||||
|
*
|
||||||
|
* @param event connected event
|
||||||
|
*/
|
||||||
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
|
public void onConnected(ConnectedToProtocolServerEvent event) {
|
||||||
|
broker.notifyServerConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles packets coming from INS and the web server side.
|
||||||
|
*
|
||||||
|
* @param event packet event
|
||||||
|
*/
|
||||||
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
|
public void onPacketRead(C_PacketReadEvent event) {
|
||||||
|
Object p = event.getPacket();
|
||||||
|
|
||||||
|
if (p instanceof INSResponsePacket resp) {
|
||||||
|
onInsResponse(resp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p instanceof WebResponsePacket resp) {
|
||||||
|
broker.onWebResponse(resp.getStatusCode(), resp.getContentType(), resp.getHeaders(), resp.getBody());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p instanceof WebStreamStartPacket_v1_0_0_B start) {
|
||||||
|
broker.onStreamStart(start.getStatusCode(), start.getContentType(), start.getHeaders(), start.getTotalLength());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p instanceof WebStreamChunkPacket_v1_0_0_B chunk) {
|
||||||
|
broker.onStreamChunk(chunk.getSeq(), chunk.getData());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p instanceof WebStreamEndPacket_v1_0_0_B end) {
|
||||||
|
broker.onStreamEnd(end.isOk());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onInsResponse(INSResponsePacket resp) {
|
||||||
|
INSResponseStatus status = resp.getStatus();
|
||||||
|
List<INSRecord> records = resp.getRecords();
|
||||||
|
|
||||||
|
if (status != INSResponseStatus.OK) {
|
||||||
|
broker.invalidateCurrentInfoName();
|
||||||
|
throw new IllegalStateException("INS resolution failed: " + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (records == null || records.isEmpty() || records.getFirst() == null || records.getFirst().value == null) {
|
||||||
|
broker.invalidateCurrentInfoName();
|
||||||
|
throw new IllegalStateException("INS resolution returned no usable records");
|
||||||
|
}
|
||||||
|
|
||||||
|
String host = records.getFirst().value.trim();
|
||||||
|
if (host.isEmpty()) {
|
||||||
|
broker.invalidateCurrentInfoName();
|
||||||
|
throw new IllegalStateException("INS record value is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
String hostname;
|
||||||
|
int port;
|
||||||
|
|
||||||
|
if (!host.contains(":")) {
|
||||||
|
hostname = host;
|
||||||
|
|
||||||
|
if (records.getFirst().port == 0) port = 1028;
|
||||||
|
else port = records.getFirst().port;
|
||||||
|
} else {
|
||||||
|
String[] split = host.split(":", 2);
|
||||||
|
hostname = split[0].trim();
|
||||||
|
String p1 = split[1].trim();
|
||||||
|
if (hostname.isEmpty() || p1.isEmpty()) {
|
||||||
|
broker.invalidateCurrentInfoName();
|
||||||
|
throw new IllegalStateException("Invalid INS host:port value: " + host);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
port = Integer.parseInt(p1);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
broker.invalidateCurrentInfoName();
|
||||||
|
throw new IllegalStateException("Invalid port in INS record: " + host, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread t = new Thread(() -> connectServer(hostname, port), "oac-web-server-connect");
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connectServer(String hostname, int port) {
|
||||||
|
try {
|
||||||
|
if (client.getClientServerConnection() != null && client.getClientServerConnection().isConnected()) {
|
||||||
|
client.getClientServerConnection().disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
client.buildServerConnection(null, client.getProtocolBridge().getProtocolValues().ssl);
|
||||||
|
client.getClientServerConnection().connect(hostname, port);
|
||||||
|
} catch (Exception e) {
|
||||||
|
broker.invalidateCurrentInfoName();
|
||||||
|
impl.serverConnectionFailed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,520 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Central broker that translates {@code web://} URLs into OAC protocol traffic.
|
||||||
|
*
|
||||||
|
* <p>Protocol limitation: no correlation id -> single-flight (one in-flight request at a time).</p>
|
||||||
|
*
|
||||||
|
* <p>UDP streaming semantics (best-effort):</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>Chunks may arrive out of order or be lost.</li>
|
||||||
|
* <li>We accept gaps and assemble what we have after {@code WebStreamEndPacket}.</li>
|
||||||
|
* <li>We wait a short grace window after stream end to allow late UDP packets.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class OacWebRequestBroker {
|
||||||
|
|
||||||
|
private static final OacWebRequestBroker INSTANCE = new OacWebRequestBroker();
|
||||||
|
|
||||||
|
private static final long CONNECT_TIMEOUT_SECONDS = 10;
|
||||||
|
private static final long RESPONSE_TIMEOUT_SECONDS = 25;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grace time after receiving WebStreamEndPacket to allow late UDP packets (reordering).
|
||||||
|
*/
|
||||||
|
private static final long UDP_END_GRACE_MILLIS = 150;
|
||||||
|
|
||||||
|
private final Object responseLock = new Object();
|
||||||
|
|
||||||
|
private volatile ProtocolClient client;
|
||||||
|
private volatile CountDownLatch connectionLatch;
|
||||||
|
private volatile String currentInfoName;
|
||||||
|
private volatile ResponseState responseState;
|
||||||
|
|
||||||
|
private OacWebRequestBroker() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the singleton broker.
|
||||||
|
*
|
||||||
|
* @return broker
|
||||||
|
*/
|
||||||
|
public static OacWebRequestBroker get() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String safeContentType(String ct) {
|
||||||
|
return (ct == null || ct.isBlank()) ? "application/octet-stream" : ct;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, String> safeHeaders(Map<String, String> headers) {
|
||||||
|
return (headers == null || headers.isEmpty()) ? Map.of() : Map.copyOf(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalizePathWithQuery(String path, String query) {
|
||||||
|
String p;
|
||||||
|
if (path == null || path.isBlank() || "/".equals(path)) {
|
||||||
|
p = "index.html";
|
||||||
|
} else {
|
||||||
|
p = path.startsWith("/") ? path.substring(1) : path;
|
||||||
|
if (p.isBlank()) p = "index.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query != null && !query.isBlank()) {
|
||||||
|
return p + "?" + query;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assembles the response body from received chunks in ascending seq order.
|
||||||
|
*
|
||||||
|
* <p>Best-effort UDP behavior: gaps are ignored.</p>
|
||||||
|
*/
|
||||||
|
private static void assembleBestEffortLocked(ResponseState st) {
|
||||||
|
st.body.reset();
|
||||||
|
|
||||||
|
if (st.chunkBuffer.isEmpty() || st.maxSeqSeen < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int seq = 0; seq <= st.maxSeqSeen; seq++) {
|
||||||
|
byte[] chunk = st.chunkBuffer.get(seq);
|
||||||
|
if (chunk == null) continue; // gap accepted
|
||||||
|
st.body.write(chunk, 0, chunk.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sleepSilently(long millis) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(millis);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches the client used to send INS/Web packets.
|
||||||
|
*
|
||||||
|
* @param client protocol client
|
||||||
|
*/
|
||||||
|
public void attachClient(ProtocolClient client) {
|
||||||
|
this.client = Objects.requireNonNull(client, "client");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy overload (GET with no body).
|
||||||
|
*
|
||||||
|
* @param url web:// URL
|
||||||
|
* @param headers request headers
|
||||||
|
* @return response
|
||||||
|
*/
|
||||||
|
public OacWebResponse fetch(URL url, Map<String, String> headers) {
|
||||||
|
return fetch(url, WebRequestMethod.GET, headers, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a URL via OAC protocol (used by {@link java.net.URLConnection}).
|
||||||
|
*
|
||||||
|
* @param url web:// URL
|
||||||
|
* @param method request method
|
||||||
|
* @param headers request headers
|
||||||
|
* @param body request body (may be null)
|
||||||
|
* @return response
|
||||||
|
*/
|
||||||
|
public OacWebResponse fetch(URL url, WebRequestMethod method, Map<String, String> headers, byte[] body) {
|
||||||
|
Objects.requireNonNull(url, "url");
|
||||||
|
Objects.requireNonNull(method, "method");
|
||||||
|
|
||||||
|
ProtocolClient c = this.client;
|
||||||
|
if (c == null) {
|
||||||
|
throw new IllegalStateException("ProtocolClient not attached. Call OacWebUrlInstaller.installOnce(..., client) first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Response r = openAndAwait(c, url, method, headers, body);
|
||||||
|
|
||||||
|
byte[] respBody = (r.body() == null) ? new byte[0] : r.body();
|
||||||
|
long len = respBody.length;
|
||||||
|
|
||||||
|
return new OacWebResponse(
|
||||||
|
r.statusCode(),
|
||||||
|
r.contentType(),
|
||||||
|
OacWebResponse.safeHeaders(r.headers()),
|
||||||
|
new ByteArrayInputStream(respBody),
|
||||||
|
len
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a resource and blocks until the current single-flight response completes.
|
||||||
|
*/
|
||||||
|
public Response openAndAwait(ProtocolClient client, URL url, WebRequestMethod method, Map<String, String> headers, byte[] body) {
|
||||||
|
Objects.requireNonNull(client, "client");
|
||||||
|
Objects.requireNonNull(url, "url");
|
||||||
|
Objects.requireNonNull(method, "method");
|
||||||
|
|
||||||
|
open(client, url, method, headers, body);
|
||||||
|
return awaitResponse(RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends required packets for a {@code web://} URL.
|
||||||
|
*/
|
||||||
|
public synchronized void open(ProtocolClient client, URL url, WebRequestMethod method, Map<String, String> headers, byte[] body) {
|
||||||
|
Objects.requireNonNull(client, "client");
|
||||||
|
Objects.requireNonNull(url, "url");
|
||||||
|
Objects.requireNonNull(method, "method");
|
||||||
|
|
||||||
|
if (!"web".equalsIgnoreCase(url.getProtocol())) {
|
||||||
|
throw new IllegalArgumentException("Unsupported protocol: " + url.getProtocol());
|
||||||
|
}
|
||||||
|
|
||||||
|
String infoName = url.getHost();
|
||||||
|
if (infoName == null || infoName.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("Missing InfoName in URL: " + url);
|
||||||
|
}
|
||||||
|
|
||||||
|
String path = normalizePathWithQuery(url.getPath(), url.getQuery());
|
||||||
|
|
||||||
|
beginNewResponse();
|
||||||
|
|
||||||
|
if (!infoName.equals(currentInfoName)) {
|
||||||
|
resolveAndConnect(client, infoName);
|
||||||
|
currentInfoName = infoName;
|
||||||
|
} else {
|
||||||
|
awaitConnectionIfPending();
|
||||||
|
}
|
||||||
|
|
||||||
|
sendWebRequest(client, path, method, headers, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by packet listener when server connection is established.
|
||||||
|
*/
|
||||||
|
public void notifyServerConnected() {
|
||||||
|
CountDownLatch latch = connectionLatch;
|
||||||
|
if (latch != null) {
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates cached InfoName, forcing a new INS resolution on next request.
|
||||||
|
*/
|
||||||
|
public synchronized void invalidateCurrentInfoName() {
|
||||||
|
currentInfoName = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a non-streamed WebResponsePacket.
|
||||||
|
*
|
||||||
|
* @param statusCode status code
|
||||||
|
* @param contentType content-type
|
||||||
|
* @param headers headers
|
||||||
|
* @param body body
|
||||||
|
*/
|
||||||
|
public void onWebResponse(int statusCode, String contentType, Map<String, String> headers, byte[] body) {
|
||||||
|
ResponseState st = responseState;
|
||||||
|
if (st == null) return;
|
||||||
|
|
||||||
|
synchronized (responseLock) {
|
||||||
|
if (st.completed) return;
|
||||||
|
|
||||||
|
st.statusCode = statusCode;
|
||||||
|
st.contentType = safeContentType(contentType);
|
||||||
|
st.headers = safeHeaders(headers);
|
||||||
|
|
||||||
|
byte[] b = (body == null) ? new byte[0] : body;
|
||||||
|
st.body.reset();
|
||||||
|
st.body.write(b, 0, b.length);
|
||||||
|
|
||||||
|
st.completed = true;
|
||||||
|
st.success = true;
|
||||||
|
st.done.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the beginning of a streamed response.
|
||||||
|
*
|
||||||
|
* @param statusCode status code
|
||||||
|
* @param contentType content-type
|
||||||
|
* @param headers headers
|
||||||
|
* @param totalLength total length (may be -1)
|
||||||
|
*/
|
||||||
|
public void onStreamStart(int statusCode, String contentType, Map<String, String> headers, long totalLength) {
|
||||||
|
ResponseState st = responseState;
|
||||||
|
if (st == null) return;
|
||||||
|
|
||||||
|
synchronized (responseLock) {
|
||||||
|
if (st.completed) return;
|
||||||
|
|
||||||
|
st.statusCode = statusCode;
|
||||||
|
st.contentType = safeContentType(contentType);
|
||||||
|
st.headers = safeHeaders(headers);
|
||||||
|
st.totalLength = totalLength;
|
||||||
|
|
||||||
|
st.streamStarted = true;
|
||||||
|
st.maxSeqSeen = -1;
|
||||||
|
st.endReceived = false;
|
||||||
|
st.endReceivedAtMillis = 0L;
|
||||||
|
|
||||||
|
// Streaming body will be assembled on end (best-effort UDP)
|
||||||
|
st.body.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a streamed chunk.
|
||||||
|
*
|
||||||
|
* <p>UDP best-effort: store by seq and assemble later; accept gaps.</p>
|
||||||
|
*
|
||||||
|
* @param seq chunk sequence number
|
||||||
|
* @param data chunk bytes
|
||||||
|
*/
|
||||||
|
public void onStreamChunk(int seq, byte[] data) {
|
||||||
|
ResponseState st = responseState;
|
||||||
|
if (st == null) return;
|
||||||
|
if (data == null || data.length == 0) return;
|
||||||
|
|
||||||
|
synchronized (responseLock) {
|
||||||
|
if (st.completed) return;
|
||||||
|
|
||||||
|
if (!st.streamStarted) {
|
||||||
|
failLocked(st, "Stream chunk received before stream start");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seq < 0) return;
|
||||||
|
|
||||||
|
st.chunkBuffer.put(seq, Arrays.copyOf(data, data.length));
|
||||||
|
if (seq > st.maxSeqSeen) st.maxSeqSeen = seq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles stream end.
|
||||||
|
*
|
||||||
|
* <p>UDP best-effort: do not complete immediately; allow late UDP packets and assemble after grace.</p>
|
||||||
|
*
|
||||||
|
* @param ok end status
|
||||||
|
*/
|
||||||
|
public void onStreamEnd(boolean ok) {
|
||||||
|
ResponseState st = responseState;
|
||||||
|
if (st == null) return;
|
||||||
|
|
||||||
|
synchronized (responseLock) {
|
||||||
|
if (st.completed) return;
|
||||||
|
|
||||||
|
if (!st.streamStarted) {
|
||||||
|
st.streamStarted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
st.success = ok;
|
||||||
|
st.endReceived = true;
|
||||||
|
st.endReceivedAtMillis = System.currentTimeMillis();
|
||||||
|
// completion + assembly happens in awaitResponse() after grace window
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for the current response (single-flight).
|
||||||
|
*
|
||||||
|
* @param timeout timeout
|
||||||
|
* @param unit unit
|
||||||
|
* @return response
|
||||||
|
*/
|
||||||
|
public Response awaitResponse(long timeout, TimeUnit unit) {
|
||||||
|
Objects.requireNonNull(unit, "unit");
|
||||||
|
|
||||||
|
ResponseState st = responseState;
|
||||||
|
if (st == null) {
|
||||||
|
throw new IllegalStateException("No in-flight request");
|
||||||
|
}
|
||||||
|
|
||||||
|
long deadlineNanos = System.nanoTime() + unit.toNanos(timeout);
|
||||||
|
|
||||||
|
for (; ; ) {
|
||||||
|
synchronized (responseLock) {
|
||||||
|
if (st.completed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-stream response already completed by onWebResponse()
|
||||||
|
if (!st.streamStarted && st.done.getCount() == 0) {
|
||||||
|
st.completed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (st.endReceived) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (now - st.endReceivedAtMillis >= UDP_END_GRACE_MILLIS) {
|
||||||
|
// Assemble best-effort body from received chunks
|
||||||
|
assembleBestEffortLocked(st);
|
||||||
|
|
||||||
|
st.completed = true;
|
||||||
|
|
||||||
|
if (!st.success) {
|
||||||
|
st.errorMessage = (st.errorMessage == null) ? "Streaming failed" : st.errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
st.done.countDown();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (System.nanoTime() >= deadlineNanos) {
|
||||||
|
throw new IllegalStateException("Timeout while waiting for Web response");
|
||||||
|
}
|
||||||
|
|
||||||
|
sleepSilently(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (responseLock) {
|
||||||
|
if (!st.success) {
|
||||||
|
throw new IllegalStateException(st.errorMessage == null ? "Request failed" : st.errorMessage);
|
||||||
|
}
|
||||||
|
return new Response(
|
||||||
|
st.statusCode,
|
||||||
|
st.contentType,
|
||||||
|
st.headers,
|
||||||
|
st.body.toByteArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolveAndConnect(ProtocolClient client, String infoName) {
|
||||||
|
if (client.getClientINSConnection() == null || !client.getClientINSConnection().isConnected()) return;
|
||||||
|
|
||||||
|
String[] parts = infoName.split("\\.");
|
||||||
|
if (parts.length < 2 || parts.length > 3) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Invalid INS address format: " + infoName + " (expected name.tln or sub.name.tln)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String tln = parts[parts.length - 1];
|
||||||
|
String name = parts[parts.length - 2];
|
||||||
|
String sub = (parts.length == 3) ? parts[0] : null;
|
||||||
|
|
||||||
|
connectionLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
try {
|
||||||
|
client.sendINSQuery(tln, name, sub, INSRecordType.A);
|
||||||
|
awaitConnectionIfPending();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException("Failed to send INSQueryPacket for " + infoName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void awaitConnectionIfPending() {
|
||||||
|
CountDownLatch latch = connectionLatch;
|
||||||
|
if (latch == null || client == null || client.getClientServerConnection() == null || client.getClientINSConnection() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!latch.await(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
|
||||||
|
throw new IllegalStateException("Timeout while waiting for ServerConnection");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new IllegalStateException("Interrupted while waiting for ServerConnection", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendWebRequest(ProtocolClient client, String path, WebRequestMethod method, Map<String, String> headers, byte[] body) {
|
||||||
|
Objects.requireNonNull(client, "client");
|
||||||
|
Objects.requireNonNull(path, "path");
|
||||||
|
Objects.requireNonNull(method, "method");
|
||||||
|
|
||||||
|
if (client.getClientServerConnection() == null || !client.getClientServerConnection().isConnected()) {
|
||||||
|
awaitConnectionIfPending();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.getClientServerConnection() == null || !client.getClientServerConnection().isConnected()) {
|
||||||
|
throw new IllegalStateException("ServerConnection is not connected after waiting");
|
||||||
|
}
|
||||||
|
|
||||||
|
WebRequestPacket packet = new WebRequestPacket(
|
||||||
|
path,
|
||||||
|
method,
|
||||||
|
(headers == null ? Map.of() : headers),
|
||||||
|
body
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
client.getClientServerConnection().sendPacket(packet, TransportProtocol.TCP);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException("Failed to send WebRequestPacket for path " + path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void beginNewResponse() {
|
||||||
|
synchronized (responseLock) {
|
||||||
|
responseState = new ResponseState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void failLocked(ResponseState st, String message) {
|
||||||
|
st.completed = true;
|
||||||
|
st.success = false;
|
||||||
|
st.errorMessage = message;
|
||||||
|
st.done.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response DTO.
|
||||||
|
*
|
||||||
|
* @param statusCode status code
|
||||||
|
* @param contentType content type
|
||||||
|
* @param headers headers
|
||||||
|
* @param body body bytes
|
||||||
|
*/
|
||||||
|
public record Response(int statusCode, String contentType, Map<String, String> headers, byte[] body) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-flight state for a single request (single-flight).
|
||||||
|
*/
|
||||||
|
private static final class ResponseState {
|
||||||
|
private final CountDownLatch done = new CountDownLatch(1);
|
||||||
|
|
||||||
|
private final ByteArrayOutputStream body = new ByteArrayOutputStream(64 * 1024);
|
||||||
|
private final Map<Integer, byte[]> chunkBuffer = new HashMap<>();
|
||||||
|
|
||||||
|
private int statusCode = 0;
|
||||||
|
private String contentType = "application/octet-stream";
|
||||||
|
private Map<String, String> headers = Map.of();
|
||||||
|
|
||||||
|
private boolean streamStarted;
|
||||||
|
private long totalLength;
|
||||||
|
|
||||||
|
private int maxSeqSeen = -1;
|
||||||
|
|
||||||
|
private boolean endReceived;
|
||||||
|
private long endReceivedAtMillis;
|
||||||
|
|
||||||
|
private boolean completed;
|
||||||
|
private boolean success;
|
||||||
|
private String errorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a resolved web response for the JavaFX WebView.
|
||||||
|
*
|
||||||
|
* @param statusCode HTTP-like status code
|
||||||
|
* @param contentType response content-type (as sent by server)
|
||||||
|
* @param headers response headers
|
||||||
|
* @param bodyStream body stream (may be streaming)
|
||||||
|
* @param contentLength content length if known, else -1
|
||||||
|
*/
|
||||||
|
public record OacWebResponse(
|
||||||
|
int statusCode,
|
||||||
|
String contentType,
|
||||||
|
Map<String, String> headers,
|
||||||
|
InputStream bodyStream,
|
||||||
|
long contentLength
|
||||||
|
) {
|
||||||
|
public OacWebResponse {
|
||||||
|
Objects.requireNonNull(headers, "headers");
|
||||||
|
Objects.requireNonNull(bodyStream, "bodyStream");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> safeHeaders(Map<String, String> h) {
|
||||||
|
return (h == null) ? Collections.emptyMap() : Collections.unmodifiableMap(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta;
|
||||||
|
|
||||||
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the "web://" protocol handler using java.protocol.handler.pkgs.
|
||||||
|
* Recommended to use Version v1.0.0-BETA or newer
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.3")
|
||||||
|
public final class OacWebUrlInstaller_v1_0_0_B {
|
||||||
|
|
||||||
|
private static final AtomicBoolean INSTALLED = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
private OacWebUrlInstaller_v1_0_0_B() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void installOnce(ProtocolBridge protocolBridge, LibClientImpl_v1_0_0_B impl) {
|
||||||
|
Objects.requireNonNull(protocolBridge, "protocolBridge");
|
||||||
|
Objects.requireNonNull(impl, "impl");
|
||||||
|
|
||||||
|
if (!INSTALLED.compareAndSet(false, true)) return;
|
||||||
|
|
||||||
|
OacWebRequestBroker.get().attachClient(protocolBridge.getProtocolClient());
|
||||||
|
protocolBridge.getProtocolValues().eventManager.
|
||||||
|
registerListener(new OacWebPacketListener(OacWebRequestBroker.get(), protocolBridge.getProtocolClient(), impl));
|
||||||
|
|
||||||
|
ProtocolHandlerPackages.installPackage("org.openautonomousconnection.urlhandler");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to safely append packages to "java.protocol.handler.pkgs".
|
||||||
|
*/
|
||||||
|
public final class ProtocolHandlerPackages {
|
||||||
|
|
||||||
|
private static final String KEY = "java.protocol.handler.pkgs";
|
||||||
|
|
||||||
|
private ProtocolHandlerPackages() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a package prefix to the protocol handler search path.
|
||||||
|
*
|
||||||
|
* @param pkg package prefix (e.g. "com.example.protocols")
|
||||||
|
*/
|
||||||
|
public static void installPackage(String pkg) {
|
||||||
|
Objects.requireNonNull(pkg, "pkg");
|
||||||
|
String p = pkg.trim();
|
||||||
|
if (p.isEmpty()) return;
|
||||||
|
|
||||||
|
String existing = System.getProperty(KEY, "");
|
||||||
|
Set<String> parts = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
if (!existing.isBlank()) {
|
||||||
|
for (String s : existing.split("\\|")) {
|
||||||
|
String t = s.trim();
|
||||||
|
if (!t.isEmpty()) parts.add(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parts.add(p);
|
||||||
|
|
||||||
|
String merged = String.join("|", parts);
|
||||||
|
System.setProperty(KEY, merged);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta.web;
|
||||||
|
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta.OacSessionJar;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta.OacWebHttpURLConnection;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta.OacWebRequestBroker;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.net.URLStreamHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URLStreamHandler for the "web" protocol (loaded via java.protocol.handler.pkgs).
|
||||||
|
*/
|
||||||
|
public final class Handler extends URLStreamHandler {
|
||||||
|
private static final OacSessionJar SESSION_JAR = new OacSessionJar();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected URLConnection openConnection(URL u) throws IOException {
|
||||||
|
return new OacWebHttpURLConnection(u, OacWebRequestBroker.get(), SESSION_JAR);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientDisconnectedEvent;
|
||||||
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
|
||||||
|
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolINSServerEvent;
|
||||||
|
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolServerEvent;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.OacWebRequestBroker;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coordinates INS resolution for WEB requests and connects to the resolved server endpoint.
|
||||||
|
*
|
||||||
|
* <p>No {@code sendIns()} exists. This coordinator sends INS queries directly:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #requestResolve(String)} is called on every WEB request.</li>
|
||||||
|
* <li>Requests for the currently connected InfoName reuse the active server connection.</li>
|
||||||
|
* <li>If INS is connected, unresolved hosts immediately trigger {@code sendINSQuery(tln,name,sub,A)}.</li>
|
||||||
|
* <li>If INS is not connected yet, it stores the pending host and sends on {@link ConnectedToProtocolINSServerEvent}.</li>
|
||||||
|
* <li>On {@link INSResponsePacket} it connects the server connection.</li>
|
||||||
|
* <li>On {@link ConnectedToProtocolServerEvent} it opens the broker gate.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class InsResolutionCoordinator_v1_0_1_B extends EventListener {
|
||||||
|
|
||||||
|
private static final InsResolutionCoordinator_v1_0_1_B INSTANCE = new InsResolutionCoordinator_v1_0_1_B();
|
||||||
|
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
private volatile ProtocolBridge bridge;
|
||||||
|
|
||||||
|
private volatile ProtocolClient insClient; // from ConnectedToProtocolINSServerEvent
|
||||||
|
private volatile boolean insConnected;
|
||||||
|
private volatile Object activeServerConnection;
|
||||||
|
|
||||||
|
private volatile String activeInfoName;
|
||||||
|
private volatile String pendingInfoName;
|
||||||
|
|
||||||
|
private InsResolutionCoordinator_v1_0_1_B() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return global singleton instance
|
||||||
|
*/
|
||||||
|
public static InsResolutionCoordinator_v1_0_1_B get() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches runtime dependencies.
|
||||||
|
*
|
||||||
|
* @param protocolBridge protocol bridge
|
||||||
|
*/
|
||||||
|
public synchronized void attach(ProtocolBridge protocolBridge) {
|
||||||
|
Objects.requireNonNull(protocolBridge, "protocolBridge");
|
||||||
|
|
||||||
|
if (this.bridge == null) {
|
||||||
|
this.bridge = protocolBridge;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.bridge == protocolBridge) return;
|
||||||
|
|
||||||
|
throw new IllegalStateException("InsResolutionCoordinator runtime already initialized with different instances");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the WEB broker for EVERY outgoing request.
|
||||||
|
*
|
||||||
|
* <p>If the requested InfoName is already connected, the current server connection is reused.
|
||||||
|
* Otherwise an INS query is triggered for the URL host. If INS is not connected yet, the host is stored
|
||||||
|
* and will be sent once INS connects.</p>
|
||||||
|
*
|
||||||
|
* @param infoName host from {@code web://<infoName>/...}
|
||||||
|
*/
|
||||||
|
public void requestResolve(String infoName) {
|
||||||
|
String in = (infoName == null) ? "" : infoName.trim();
|
||||||
|
if (in.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("InfoName is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canReuseCurrentServerConnection(in)) {
|
||||||
|
OacWebRequestBroker.get().notifyServerConnected();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (lock) {
|
||||||
|
pendingInfoName = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unresolved or changed hosts reset the server gate and trigger INS -> connect -> request.
|
||||||
|
OacWebRequestBroker.get().beginServerConnectAttempt();
|
||||||
|
|
||||||
|
// If INS already connected, send immediately
|
||||||
|
trySendPendingInsNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INS is now connected -> we can send pending INS immediately.
|
||||||
|
*
|
||||||
|
* @param event ins connected event
|
||||||
|
*/
|
||||||
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
|
public void onConnectedToIns(ConnectedToProtocolINSServerEvent event) {
|
||||||
|
Objects.requireNonNull(event, "event");
|
||||||
|
|
||||||
|
this.insClient = Objects.requireNonNull(event.getClient(), "event.getClient()");
|
||||||
|
this.insConnected = true;
|
||||||
|
|
||||||
|
// Send pending (if any) now that INS is connected
|
||||||
|
trySendPendingInsNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives INS resolution response and starts the WEB server connect attempt.
|
||||||
|
*
|
||||||
|
* @param event packet read event
|
||||||
|
*/
|
||||||
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
|
public void onInsResponse(C_PacketReadEvent event) {
|
||||||
|
Objects.requireNonNull(event, "event");
|
||||||
|
if (!(event.getPacket() instanceof INSResponsePacket packet)) return;
|
||||||
|
|
||||||
|
final String infoName;
|
||||||
|
final String hostname;
|
||||||
|
final int port;
|
||||||
|
|
||||||
|
try {
|
||||||
|
synchronized (lock) {
|
||||||
|
infoName = pendingInfoName;
|
||||||
|
}
|
||||||
|
if (infoName == null || infoName.isBlank()) {
|
||||||
|
throw new IllegalStateException("INS response received without pending info name");
|
||||||
|
}
|
||||||
|
|
||||||
|
INSResponseStatus status = packet.getStatus();
|
||||||
|
List<INSRecord> records = packet.getRecords();
|
||||||
|
|
||||||
|
if (status != INSResponseStatus.OK) {
|
||||||
|
throw new IllegalStateException("INS resolution failed: " + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (records == null || records.isEmpty() || records.getFirst() == null || records.getFirst().value == null) {
|
||||||
|
throw new IllegalStateException("INS resolution returned no usable records");
|
||||||
|
}
|
||||||
|
|
||||||
|
String host = records.getFirst().value.trim();
|
||||||
|
if (host.isEmpty()) {
|
||||||
|
throw new IllegalStateException("INS record value is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!host.contains(":")) {
|
||||||
|
hostname = host;
|
||||||
|
|
||||||
|
int recordPort = records.getFirst().port;
|
||||||
|
port = (recordPort == 0) ? 1028 : recordPort;
|
||||||
|
} else {
|
||||||
|
String[] split = host.split(":", 2);
|
||||||
|
String h = split[0].trim();
|
||||||
|
String p1 = split[1].trim();
|
||||||
|
|
||||||
|
if (h.isEmpty() || p1.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Invalid INS host:port value: " + host);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
port = Integer.parseInt(p1);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalStateException("Invalid port in INS record: " + host, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname = h;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
OacWebRequestBroker.get().notifyServerConnectionFailed(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread t = new Thread(() -> connectServer(infoName, hostname, port), "oac-web-server-connect");
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server connected -> open broker gate.
|
||||||
|
*
|
||||||
|
* @param event server connected event
|
||||||
|
*/
|
||||||
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
|
public void onConnectedToServer(ConnectedToProtocolServerEvent event) {
|
||||||
|
Objects.requireNonNull(event, "event");
|
||||||
|
ProtocolClient client = event.getClient();
|
||||||
|
if (client != null) {
|
||||||
|
this.activeServerConnection = client.getClientServerConnection();
|
||||||
|
}
|
||||||
|
OacWebRequestBroker.get().notifyServerConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
|
public void onDisconnected(ClientDisconnectedEvent event) {
|
||||||
|
Objects.requireNonNull(event, "event");
|
||||||
|
|
||||||
|
Object disconnected = event.getClient();
|
||||||
|
if (disconnected == null) return;
|
||||||
|
|
||||||
|
ProtocolBridge b = this.bridge;
|
||||||
|
ProtocolClient client = (b == null) ? null : b.getProtocolClient();
|
||||||
|
|
||||||
|
if (disconnected == activeServerConnection) {
|
||||||
|
activeServerConnection = null;
|
||||||
|
activeInfoName = null;
|
||||||
|
OacWebRequestBroker.get().notifyServerConnectionFailed(
|
||||||
|
new IllegalStateException("Server connection was lost")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client != null && disconnected == client.getClientINSConnection()) {
|
||||||
|
this.insConnected = false;
|
||||||
|
this.pendingInfoName = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean canReuseCurrentServerConnection(String infoName) {
|
||||||
|
ProtocolBridge b = this.bridge;
|
||||||
|
if (b == null) return false;
|
||||||
|
|
||||||
|
ProtocolClient client = b.getProtocolClient();
|
||||||
|
if (client == null || client.getClientServerConnection() == null || !client.getClientServerConnection().isConnected()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return infoName.equalsIgnoreCase(activeInfoName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trySendPendingInsNow() {
|
||||||
|
ProtocolClient c = this.insClient;
|
||||||
|
if (c == null) return;
|
||||||
|
|
||||||
|
final String infoName;
|
||||||
|
synchronized (lock) {
|
||||||
|
infoName = pendingInfoName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (infoName == null || infoName.isBlank()) return;
|
||||||
|
|
||||||
|
InsParts parts = InsParts.parse(infoName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
c.sendINSQuery(parts.tln(), parts.name(), parts.sub(), INSRecordType.A);
|
||||||
|
} catch (Exception e) {
|
||||||
|
OacWebRequestBroker.get().notifyServerConnectionFailed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connectServer(String infoName, String hostname, int port) {
|
||||||
|
ProtocolBridge b = this.bridge;
|
||||||
|
if (b == null) return;
|
||||||
|
if (canReuseCurrentServerConnection(infoName)) {
|
||||||
|
OacWebRequestBroker.get().notifyServerConnected();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ProtocolClient client = b.getProtocolClient();
|
||||||
|
|
||||||
|
if (client.getClientServerConnection() != null && client.getClientServerConnection().isConnected()) {
|
||||||
|
client.getClientServerConnection().disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
client.buildServerConnection(
|
||||||
|
null,
|
||||||
|
client.getProtocolBridge().getProtocolValues().ssl
|
||||||
|
);
|
||||||
|
|
||||||
|
client.getClientServerConnection().connect(hostname, port);
|
||||||
|
this.activeServerConnection = client.getClientServerConnection();
|
||||||
|
this.activeInfoName = infoName;
|
||||||
|
// Broker gate is opened by ConnectedToProtocolServerEvent.
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
this.activeServerConnection = null;
|
||||||
|
this.activeInfoName = null;
|
||||||
|
OacWebRequestBroker.get().notifyServerConnectionFailed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses name.tln or sub.name.tln.
|
||||||
|
*/
|
||||||
|
public record InsParts(String tln, String name, String sub) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an InfoName into INS parts.
|
||||||
|
*
|
||||||
|
* @param infoName infoName (name.tln or sub.name.tln)
|
||||||
|
* @return parts
|
||||||
|
*/
|
||||||
|
public static InsParts parse(String infoName) {
|
||||||
|
String in = Objects.requireNonNull(infoName, "infoName").trim();
|
||||||
|
if (in.isEmpty()) throw new IllegalArgumentException("infoName is empty");
|
||||||
|
|
||||||
|
String[] parts = in.split("\\.");
|
||||||
|
if (parts.length < 2 || parts.length > 3) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Invalid INS address format: " + infoName + " (expected name.tln or sub.name.tln)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String tln = parts[parts.length - 1];
|
||||||
|
String name = parts[parts.length - 2];
|
||||||
|
String sub = (parts.length == 3) ? parts[0] : null;
|
||||||
|
|
||||||
|
return new InsParts(tln, name, sub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta;
|
||||||
|
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta.LibClientImpl_v1_0_0_B;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.WebFlagInspector;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.WebRequestContextProvider;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback surface for URL-handler related streaming events (v1.0.1-BETA).
|
||||||
|
*/
|
||||||
|
public abstract class LibClientImpl_v1_0_1_B extends LibClientImpl_v1_0_0_B implements WebRequestContextProvider, WebFlagInspector {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a streamed response begins.
|
||||||
|
*/
|
||||||
|
public void streamStart(
|
||||||
|
WebPacketHeader header,
|
||||||
|
int statusCode,
|
||||||
|
String contentType,
|
||||||
|
Map<String, String> headers,
|
||||||
|
long totalLength
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called for each streamed chunk.
|
||||||
|
*/
|
||||||
|
public void streamChunk(
|
||||||
|
WebPacketHeader header,
|
||||||
|
int seq,
|
||||||
|
byte[] data
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the stream ends.
|
||||||
|
*/
|
||||||
|
public void streamEnd(
|
||||||
|
WebPacketHeader header,
|
||||||
|
boolean ok,
|
||||||
|
String error
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after full best-effort assembly (only if ok=true).
|
||||||
|
*/
|
||||||
|
public void streamFinish(
|
||||||
|
WebPacketHeader header,
|
||||||
|
byte[] content
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta;
|
||||||
|
|
||||||
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.OacWebProtocolModule_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.OacWebRequestBroker;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.WebFlagInspector;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.WebRequestContextProvider;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs OAC URL protocol modules for v1.0.1-BETA.
|
||||||
|
*
|
||||||
|
* <p>Runtime behavior required by this installer:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>Every WEB request triggers an INS resolve attempt (via {@link InsResolutionCoordinator_v1_0_1_B}).</li>
|
||||||
|
* <li>Once an INS response arrives, the client connects to the resolved server endpoint.</li>
|
||||||
|
* <li>Only after the server connection is established, WEB packets may be sent (broker gate).</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Call once during startup (before creating {@link java.net.URL} instances).</p>
|
||||||
|
*/
|
||||||
|
public final class OacUrlHandlerInstaller_v1_0_1_B {
|
||||||
|
|
||||||
|
private static final AtomicBoolean INSTALLED = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
private OacUrlHandlerInstaller_v1_0_1_B() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the URL handler package path and registers the WEB module.
|
||||||
|
*
|
||||||
|
* @param protocolBridge protocol bridge
|
||||||
|
* @param impl callback implementation
|
||||||
|
* @param ctxProvider provides tab/page/frame correlation for each request
|
||||||
|
* @param flagInspector interprets {@code WebPacketHeader.flags} (e.g. stream bit)
|
||||||
|
*/
|
||||||
|
public static void installOnce(
|
||||||
|
ProtocolBridge protocolBridge,
|
||||||
|
LibClientImpl_v1_0_1_B impl,
|
||||||
|
WebRequestContextProvider ctxProvider,
|
||||||
|
WebFlagInspector flagInspector
|
||||||
|
) {
|
||||||
|
Objects.requireNonNull(protocolBridge, "protocolBridge");
|
||||||
|
Objects.requireNonNull(impl, "impl");
|
||||||
|
Objects.requireNonNull(ctxProvider, "ctxProvider");
|
||||||
|
Objects.requireNonNull(flagInspector, "flagInspector");
|
||||||
|
|
||||||
|
if (!INSTALLED.compareAndSet(false, true)) return;
|
||||||
|
|
||||||
|
ProtocolHandlerPackages.installPackage("org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta");
|
||||||
|
|
||||||
|
// WEB module (URLConnection + web packet listener -> broker)
|
||||||
|
OacWebProtocolModule_v1_0_1_B web = new OacWebProtocolModule_v1_0_1_B(ctxProvider, flagInspector, protocolBridge);
|
||||||
|
OacUrlProtocolRegistry.register(web);
|
||||||
|
web.install(protocolBridge, impl);
|
||||||
|
|
||||||
|
// INS coordination (request -> send INS -> connect server -> open broker gate)
|
||||||
|
InsResolutionCoordinator_v1_0_1_B coordinator = InsResolutionCoordinator_v1_0_1_B.get();
|
||||||
|
coordinator.attach(protocolBridge);
|
||||||
|
|
||||||
|
protocolBridge.getProtocolValues().eventManager.registerListener(coordinator);
|
||||||
|
|
||||||
|
// Broker needs to wait for "server connected" gate. Coordinator will open/fail this gate.
|
||||||
|
OacWebRequestBroker.get().attachCoordinator(coordinator);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta;
|
||||||
|
|
||||||
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pluggable module for a URL protocol scheme (e.g. "web", "ftp").
|
||||||
|
*
|
||||||
|
* <p>Each module is responsible for providing {@link URLConnection} instances for its scheme and
|
||||||
|
* wiring packet listeners into the {@link ProtocolBridge} runtime.</p>
|
||||||
|
*/
|
||||||
|
public interface OacUrlProtocolModule {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the URL scheme handled by this module (e.g. "web", "ftp")
|
||||||
|
*/
|
||||||
|
String scheme();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a connection for the given URL.
|
||||||
|
*
|
||||||
|
* @param url the URL
|
||||||
|
* @return the connection
|
||||||
|
* @throws IOException if opening fails
|
||||||
|
*/
|
||||||
|
URLConnection openConnection(URL url) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the module into the given protocol bridge (listeners, brokers, etc.).
|
||||||
|
*
|
||||||
|
* @param protocolBridge protocol bridge
|
||||||
|
* @param impl callback implementation
|
||||||
|
*/
|
||||||
|
void install(ProtocolBridge protocolBridge, LibClientImpl_v1_0_1_B impl);
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global registry for URL protocol modules.
|
||||||
|
*/
|
||||||
|
public final class OacUrlProtocolRegistry {
|
||||||
|
|
||||||
|
private static final ConcurrentHashMap<String, OacUrlProtocolModule> MODULES = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private OacUrlProtocolRegistry() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a module for its scheme.
|
||||||
|
*
|
||||||
|
* @param module the module
|
||||||
|
* @throws IllegalStateException if a different module is already registered for the scheme
|
||||||
|
*/
|
||||||
|
public static void register(OacUrlProtocolModule module) {
|
||||||
|
Objects.requireNonNull(module, "module");
|
||||||
|
|
||||||
|
String scheme = normalizeScheme(module.scheme());
|
||||||
|
OacUrlProtocolModule prev = MODULES.putIfAbsent(scheme, module);
|
||||||
|
|
||||||
|
if (prev != null && prev != module) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Module already registered for scheme '" + scheme + "': " + prev.getClass().getName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the module for the given scheme.
|
||||||
|
*
|
||||||
|
* @param scheme the scheme
|
||||||
|
* @return module or null if not registered
|
||||||
|
*/
|
||||||
|
public static OacUrlProtocolModule get(String scheme) {
|
||||||
|
if (scheme == null) return null;
|
||||||
|
return MODULES.get(normalizeScheme(scheme));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalizeScheme(String scheme) {
|
||||||
|
String s = Objects.requireNonNull(scheme, "scheme").trim();
|
||||||
|
if (s.isEmpty()) throw new IllegalArgumentException("scheme is empty");
|
||||||
|
return s.toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to safely append packages to "java.protocol.handler.pkgs".
|
||||||
|
*
|
||||||
|
* <p>JVM lookup rule: it searches {@code <pkgprefix>.<protocol>.Handler}.</p>
|
||||||
|
*/
|
||||||
|
public final class ProtocolHandlerPackages {
|
||||||
|
|
||||||
|
private static final String KEY = "java.protocol.handler.pkgs";
|
||||||
|
|
||||||
|
private ProtocolHandlerPackages() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a package prefix to the protocol handler search path.
|
||||||
|
*
|
||||||
|
* @param pkg package prefix (e.g. "org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta")
|
||||||
|
*/
|
||||||
|
public static void installPackage(String pkg) {
|
||||||
|
Objects.requireNonNull(pkg, "pkg");
|
||||||
|
String p = pkg.trim();
|
||||||
|
if (p.isEmpty()) return;
|
||||||
|
|
||||||
|
String existing = System.getProperty(KEY, "");
|
||||||
|
Set<String> parts = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
if (!existing.isBlank()) {
|
||||||
|
for (String s : existing.split("\\|")) {
|
||||||
|
String t = s.trim();
|
||||||
|
if (!t.isEmpty()) parts.add(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parts.add(p);
|
||||||
|
|
||||||
|
String merged = String.join("|", parts);
|
||||||
|
System.setProperty(KEY, merged);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InputStream wrapper that deletes one or more paths (files/directories) on close.
|
||||||
|
*
|
||||||
|
* <p>Useful for temporary downloads/streams that must be cleaned up automatically.</p>
|
||||||
|
*/
|
||||||
|
final class DeleteOnCloseInputStream extends FilterInputStream {
|
||||||
|
|
||||||
|
private final Path[] deletePaths;
|
||||||
|
private volatile boolean closed;
|
||||||
|
|
||||||
|
DeleteOnCloseInputStream(InputStream in, Path... deletePaths) {
|
||||||
|
super(Objects.requireNonNull(in, "in"));
|
||||||
|
this.deletePaths = (deletePaths == null) ? new Path[0] : deletePaths.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (closed) return;
|
||||||
|
closed = true;
|
||||||
|
|
||||||
|
IOException io = null;
|
||||||
|
try {
|
||||||
|
super.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
io = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Path p : deletePaths) {
|
||||||
|
if (p == null) continue;
|
||||||
|
try {
|
||||||
|
deleteRecursivelyIfExists(p);
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (io == null) io = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (io != null) throw io;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void deleteRecursivelyIfExists(Path path) throws IOException {
|
||||||
|
if (!Files.exists(path)) return;
|
||||||
|
|
||||||
|
if (Files.isDirectory(path)) {
|
||||||
|
Files.walkFileTree(path, new SimpleFileVisitor<>() {
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
|
Files.deleteIfExists(file);
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||||
|
Files.deleteIfExists(dir);
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.deleteIfExists(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
|
||||||
|
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.OacUrlProtocolModule;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.OacUrlProtocolRegistry;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.net.URLStreamHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URLStreamHandler for the "web" protocol.
|
||||||
|
*
|
||||||
|
* <p>Loaded by JVM via {@code java.protocol.handler.pkgs} and delegates to the registered module.</p>
|
||||||
|
*/
|
||||||
|
public final class Handler extends URLStreamHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected URLConnection openConnection(URL u) throws IOException {
|
||||||
|
OacUrlProtocolModule module = OacUrlProtocolRegistry.get("web");
|
||||||
|
if (module == null) {
|
||||||
|
throw new IOException("No module registered for scheme 'web'. Did you call OacUrlHandlerInstaller_v1_0_1_B.installOnce(...)?");
|
||||||
|
}
|
||||||
|
return module.openConnection(u);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the current session token across multiple URLConnection instances.
|
||||||
|
*/
|
||||||
|
public final class OacSessionJar {
|
||||||
|
|
||||||
|
private volatile String session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a session token (e.g. from response header "session").
|
||||||
|
*
|
||||||
|
* @param session session token
|
||||||
|
*/
|
||||||
|
public void store(String session) {
|
||||||
|
String s = (session == null) ? null : session.trim();
|
||||||
|
this.session = (s == null || s.isEmpty()) ? null : s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return stored session token or null
|
||||||
|
*/
|
||||||
|
public String get() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
|
||||||
|
|
||||||
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.ProtocolException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpURLConnection implementation that maps "web://" URLs to WEB v1.0.1 protocol requests.
|
||||||
|
*
|
||||||
|
* <p>This implementation is designed for JavaFX WebView semantics.</p>
|
||||||
|
*/
|
||||||
|
public final class OacWebHttpURLConnection extends HttpURLConnection {
|
||||||
|
|
||||||
|
private static final int MAX_REDIRECTS = 8;
|
||||||
|
|
||||||
|
private final OacWebRequestBroker broker;
|
||||||
|
private final OacSessionJar sessionJar;
|
||||||
|
private final WebRequestContextProvider ctxProvider;
|
||||||
|
private final ProtocolBridge bridge;
|
||||||
|
|
||||||
|
private final Map<String, String> requestHeaders = new LinkedHashMap<>();
|
||||||
|
private final ByteArrayOutputStream requestBody = new ByteArrayOutputStream(1024);
|
||||||
|
|
||||||
|
private boolean requestSent;
|
||||||
|
private OacWebResponse response;
|
||||||
|
|
||||||
|
public OacWebHttpURLConnection(URL url, OacWebRequestBroker broker, OacSessionJar sessionJar, WebRequestContextProvider ctxProvider, ProtocolBridge protocolBridge) {
|
||||||
|
super(url);
|
||||||
|
this.broker = Objects.requireNonNull(broker, "broker");
|
||||||
|
this.sessionJar = Objects.requireNonNull(sessionJar, "sessionJar");
|
||||||
|
this.ctxProvider = Objects.requireNonNull(ctxProvider, "ctxProvider");
|
||||||
|
this.bridge = Objects.requireNonNull(protocolBridge, "bridge");
|
||||||
|
this.method = "GET";
|
||||||
|
setInstanceFollowRedirects(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String headerValue(Map<String, String> headers, String nameLower) {
|
||||||
|
if (headers == null || headers.isEmpty() || nameLower == null) return null;
|
||||||
|
String needle = nameLower.trim().toLowerCase(Locale.ROOT);
|
||||||
|
for (Map.Entry<String, String> e : headers.entrySet()) {
|
||||||
|
if (e.getKey() == null) continue;
|
||||||
|
if (e.getKey().trim().toLowerCase(Locale.ROOT).equals(needle)) {
|
||||||
|
return e.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequestProperty(String key, String value) {
|
||||||
|
if (key == null) return;
|
||||||
|
if (requestSent) throw new IllegalStateException("Request already sent");
|
||||||
|
if (value == null) requestHeaders.remove(key);
|
||||||
|
else requestHeaders.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRequestProperty(String key) {
|
||||||
|
if (key == null) return null;
|
||||||
|
for (Map.Entry<String, String> e : requestHeaders.entrySet()) {
|
||||||
|
if (e.getKey() != null && e.getKey().equalsIgnoreCase(key)) return e.getValue();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getRequestProperties() {
|
||||||
|
Map<String, List<String>> out = new LinkedHashMap<>();
|
||||||
|
for (Map.Entry<String, String> e : requestHeaders.entrySet()) {
|
||||||
|
out.put(e.getKey(), e.getValue() == null ? List.of() : List.of(e.getValue()));
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableMap(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequestMethod(String method) throws ProtocolException {
|
||||||
|
if (method == null) throw new ProtocolException("method is null");
|
||||||
|
if (requestSent) throw new ProtocolException("Request already sent");
|
||||||
|
|
||||||
|
String m = method.trim().toUpperCase(Locale.ROOT);
|
||||||
|
if (!m.equals("GET") && !m.equals("POST")) {
|
||||||
|
throw new ProtocolException("Unsupported method: " + method);
|
||||||
|
}
|
||||||
|
this.method = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream getOutputStream() {
|
||||||
|
if (requestSent) throw new IllegalStateException("Request already sent");
|
||||||
|
setDoOutput(true);
|
||||||
|
return requestBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect() {
|
||||||
|
// Intentionally no-op: JavaFX WebView may call connect() before writing POST body.
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureResponse() throws IOException {
|
||||||
|
if (requestSent) return;
|
||||||
|
|
||||||
|
URL cur = this.url;
|
||||||
|
String methodStr = (this.method == null) ? "GET" : this.method.trim().toUpperCase(Locale.ROOT);
|
||||||
|
|
||||||
|
Map<String, String> carryHeaders = new LinkedHashMap<>(requestHeaders);
|
||||||
|
|
||||||
|
String session = sessionJar.get();
|
||||||
|
if (session != null && !session.isBlank() && headerValue(carryHeaders, "session") == null) {
|
||||||
|
carryHeaders.put("session", session);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] carryBody = null;
|
||||||
|
if (getDoOutput()) {
|
||||||
|
carryBody = requestBody.toByteArray();
|
||||||
|
if ("POST".equals(methodStr) && carryBody == null) carryBody = new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("POST".equals(methodStr) && headerValue(carryHeaders, "content-type") == null) {
|
||||||
|
carryHeaders.put("content-type", "application/x-www-form-urlencoded; charset=utf-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
OacWebResponse resp = null;
|
||||||
|
|
||||||
|
for (int i = 0; i <= MAX_REDIRECTS; i++) {
|
||||||
|
WebRequestContextProvider.WebRequestContext ctx = ctxProvider.contextFor(cur);
|
||||||
|
|
||||||
|
resp = broker.fetch(
|
||||||
|
cur,
|
||||||
|
methodStr,
|
||||||
|
carryHeaders,
|
||||||
|
carryBody,
|
||||||
|
ctx.tabId(),
|
||||||
|
ctx.pageId(),
|
||||||
|
ctx.frameId(), bridge
|
||||||
|
);
|
||||||
|
|
||||||
|
String newSession = broker.extractSession(resp.headers());
|
||||||
|
if (newSession != null && !newSession.isBlank()) {
|
||||||
|
sessionJar.store(newSession);
|
||||||
|
carryHeaders.put("session", newSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
int code = resp.statusCode();
|
||||||
|
if (!getInstanceFollowRedirects()) break;
|
||||||
|
|
||||||
|
if (code == 301 || code == 302 || code == 303 || code == 307 || code == 308) {
|
||||||
|
String loc = headerValue(resp.headers(), "location");
|
||||||
|
if (loc == null || loc.isBlank()) break;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cur = new URL(cur, loc);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code == 303) {
|
||||||
|
methodStr = "GET";
|
||||||
|
carryBody = null;
|
||||||
|
} else if ((code == 301 || code == 302) && "POST".equals(methodStr)) {
|
||||||
|
methodStr = "GET";
|
||||||
|
carryBody = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.response = resp;
|
||||||
|
this.requestSent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() throws IOException {
|
||||||
|
ensureResponse();
|
||||||
|
if (response == null) return new ByteArrayInputStream(new byte[0]);
|
||||||
|
return response.bodyStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getErrorStream() {
|
||||||
|
try {
|
||||||
|
ensureResponse();
|
||||||
|
if (response == null) return null;
|
||||||
|
return (response.statusCode() >= 400) ? response.bodyStream() : null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getResponseCode() throws IOException {
|
||||||
|
ensureResponse();
|
||||||
|
return response == null ? -1 : response.statusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContentType() {
|
||||||
|
try {
|
||||||
|
ensureResponse();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return "application/octet-stream";
|
||||||
|
}
|
||||||
|
String ct = (response == null) ? null : response.contentType();
|
||||||
|
return (ct == null || ct.isBlank()) ? "application/octet-stream" : ct;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getContentLength() {
|
||||||
|
try {
|
||||||
|
ensureResponse();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
long len = (response == null) ? -1L : response.contentLength();
|
||||||
|
return (len <= 0 || len > Integer.MAX_VALUE) ? -1 : (int) len;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getContentLengthLong() {
|
||||||
|
try {
|
||||||
|
ensureResponse();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return -1L;
|
||||||
|
}
|
||||||
|
return (response == null) ? -1L : response.contentLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getHeaderFields() {
|
||||||
|
try {
|
||||||
|
ensureResponse();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
if (response == null) return Map.of();
|
||||||
|
|
||||||
|
Map<String, List<String>> out = new LinkedHashMap<>();
|
||||||
|
for (Map.Entry<String, String> e : response.headers().entrySet()) {
|
||||||
|
String k = e.getKey();
|
||||||
|
String v = e.getValue();
|
||||||
|
if (k == null) continue;
|
||||||
|
out.put(k, v == null ? List.of() : List.of(v));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHeaderField(String name) {
|
||||||
|
if (name == null) return null;
|
||||||
|
try {
|
||||||
|
ensureResponse();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (response == null) return null;
|
||||||
|
return headerValue(response.headers(), name.trim().toLowerCase(Locale.ROOT));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnect() {
|
||||||
|
// No persistent socket owned by this object.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean usingProxy() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamChunkPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamEndPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamStartPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.LibClientImpl_v1_0_1_B;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives incoming WEB packets and forwards them into the broker and client callbacks.
|
||||||
|
*/
|
||||||
|
public final class OacWebPacketListener extends EventListener {
|
||||||
|
|
||||||
|
private final OacWebRequestBroker broker;
|
||||||
|
private final LibClientImpl_v1_0_1_B impl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a listener bound to the given broker.
|
||||||
|
*
|
||||||
|
* @param broker broker instance
|
||||||
|
* @param impl callback implementation
|
||||||
|
*/
|
||||||
|
public OacWebPacketListener(OacWebRequestBroker broker, LibClientImpl_v1_0_1_B impl) {
|
||||||
|
this.broker = Objects.requireNonNull(broker, "broker");
|
||||||
|
this.impl = Objects.requireNonNull(impl, "impl");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
|
public void onPacketRead(C_PacketReadEvent event) {
|
||||||
|
Packet p = event.getPacket();
|
||||||
|
if (p instanceof WebResourceResponsePacket resp) {
|
||||||
|
broker.onResourceResponse(resp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p instanceof WebStreamStartPacket_v1_0_1_B start) {
|
||||||
|
impl.streamStart(
|
||||||
|
start.getHeader(),
|
||||||
|
start.getStatusCode(),
|
||||||
|
start.getContentType(),
|
||||||
|
start.getHeaders() == null ? Map.of() : start.getHeaders(),
|
||||||
|
start.getTotalLength()
|
||||||
|
);
|
||||||
|
broker.onStreamStart(start);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p instanceof WebStreamChunkPacket_v1_0_1_B chunk) {
|
||||||
|
byte[] data = (chunk.getData() == null) ? new byte[0] : chunk.getData();
|
||||||
|
impl.streamChunk(chunk.getHeader(), chunk.getSeq(), data);
|
||||||
|
broker.onStreamChunk(chunk);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p instanceof WebStreamEndPacket_v1_0_1_B end) {
|
||||||
|
impl.streamEnd(end.getHeader(), end.isOk(), end.getError());
|
||||||
|
byte[] full = broker.onStreamEndAndAssemble(end);
|
||||||
|
if (full != null) {
|
||||||
|
impl.streamFinish(end.getHeader(), full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
|
||||||
|
|
||||||
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.LibClientImpl_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.OacUrlProtocolModule;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WEB protocol module implementation for v1.0.1-BETA.
|
||||||
|
*/
|
||||||
|
public final class OacWebProtocolModule_v1_0_1_B implements OacUrlProtocolModule {
|
||||||
|
|
||||||
|
private final OacSessionJar sessionJar = new OacSessionJar();
|
||||||
|
private final WebRequestContextProvider ctxProvider;
|
||||||
|
private final WebFlagInspector flagInspector;
|
||||||
|
private final ProtocolBridge bridge;
|
||||||
|
|
||||||
|
public OacWebProtocolModule_v1_0_1_B(WebRequestContextProvider ctxProvider, WebFlagInspector flagInspector, ProtocolBridge protocolBridge) {
|
||||||
|
this.ctxProvider = Objects.requireNonNull(ctxProvider, "ctxProvider");
|
||||||
|
this.flagInspector = Objects.requireNonNull(flagInspector, "flagInspector");
|
||||||
|
this.bridge = Objects.requireNonNull(protocolBridge, "protocolBridge");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String scheme() {
|
||||||
|
return "web";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URLConnection openConnection(URL url) throws IOException {
|
||||||
|
Objects.requireNonNull(url, "url");
|
||||||
|
if (!"web".equalsIgnoreCase(url.getProtocol())) {
|
||||||
|
throw new IOException("Unsupported scheme for this module: " + url.getProtocol());
|
||||||
|
}
|
||||||
|
return new OacWebHttpURLConnection(url, OacWebRequestBroker.get(), sessionJar, ctxProvider, bridge);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void install(ProtocolBridge protocolBridge, LibClientImpl_v1_0_1_B impl) {
|
||||||
|
Objects.requireNonNull(protocolBridge, "protocolBridge");
|
||||||
|
Objects.requireNonNull(impl, "impl");
|
||||||
|
|
||||||
|
OacWebRequestBroker.get().attachRuntime(protocolBridge.getProtocolClient(), flagInspector);
|
||||||
|
|
||||||
|
protocolBridge.getProtocolValues().eventManager.registerListener(
|
||||||
|
new OacWebPacketListener(OacWebRequestBroker.get(), impl)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,614 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamChunkPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamEndPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamStartPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.InsResolutionCoordinator_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketFlags;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multi-flight broker for WEB v1.0.1-BETA with best-effort streaming and temp-file assembly.
|
||||||
|
*
|
||||||
|
* <p><b>Gate behavior:</b> Before any WEB packet is sent, the broker triggers INS resolve (every request),
|
||||||
|
* then blocks until server connection is established (opened by {@link #notifyServerConnected()}).</p>
|
||||||
|
*/
|
||||||
|
public final class OacWebRequestBroker {
|
||||||
|
|
||||||
|
private static final OacWebRequestBroker INSTANCE = new OacWebRequestBroker();
|
||||||
|
|
||||||
|
private static final long RESPONSE_TIMEOUT_SECONDS = 30;
|
||||||
|
private static final long CONNECT_TIMEOUT_SECONDS = 30;
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<Long, ResponseState> inFlight = new ConcurrentHashMap<>();
|
||||||
|
private final AtomicLong requestCounter = new AtomicLong(1);
|
||||||
|
|
||||||
|
private final Object connectLock = new Object();
|
||||||
|
private volatile CompletableFuture<Void> serverReady = new CompletableFuture<>();
|
||||||
|
private volatile Throwable serverFailure;
|
||||||
|
|
||||||
|
private volatile ProtocolClient client;
|
||||||
|
private volatile WebFlagInspector flagInspector;
|
||||||
|
|
||||||
|
private volatile InsResolutionCoordinator_v1_0_1_B coordinator;
|
||||||
|
|
||||||
|
private OacWebRequestBroker() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return global singleton instance
|
||||||
|
*/
|
||||||
|
public static OacWebRequestBroker get() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches runtime dependencies required for dispatching requests and interpreting flags.
|
||||||
|
*
|
||||||
|
* @param client protocol client used to send packets
|
||||||
|
* @param flagInspector inspector for header flags (STREAM bit)
|
||||||
|
*/
|
||||||
|
public synchronized void attachRuntime(ProtocolClient client, WebFlagInspector flagInspector) {
|
||||||
|
Objects.requireNonNull(client, "client");
|
||||||
|
Objects.requireNonNull(flagInspector, "flagInspector");
|
||||||
|
|
||||||
|
if (this.client == null && this.flagInspector == null) {
|
||||||
|
this.client = client;
|
||||||
|
this.flagInspector = flagInspector;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.client == client && this.flagInspector == flagInspector) return;
|
||||||
|
|
||||||
|
throw new IllegalStateException("OacWebRequestBroker runtime already initialized with different instances");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches the INS coordinator used for per-request INS resolution.
|
||||||
|
*
|
||||||
|
* @param coordinator coordinator
|
||||||
|
*/
|
||||||
|
public synchronized void attachCoordinator(InsResolutionCoordinator_v1_0_1_B coordinator) {
|
||||||
|
Objects.requireNonNull(coordinator, "coordinator");
|
||||||
|
if (this.coordinator == null) {
|
||||||
|
this.coordinator = coordinator;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.coordinator == coordinator) return;
|
||||||
|
throw new IllegalStateException("Coordinator already attached with different instance");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the server connection gate to "not ready".
|
||||||
|
*/
|
||||||
|
public void beginServerConnectAttempt() {
|
||||||
|
synchronized (connectLock) {
|
||||||
|
this.serverFailure = null;
|
||||||
|
this.serverReady = new CompletableFuture<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the server connection gate.
|
||||||
|
*/
|
||||||
|
public void notifyServerConnected() {
|
||||||
|
synchronized (connectLock) {
|
||||||
|
this.serverFailure = null;
|
||||||
|
CompletableFuture<Void> f = this.serverReady;
|
||||||
|
if (!f.isDone()) f.complete(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fails the server connection gate and unblocks waiters.
|
||||||
|
*
|
||||||
|
* @param t failure cause
|
||||||
|
*/
|
||||||
|
public void notifyServerConnectionFailed(Throwable t) {
|
||||||
|
Objects.requireNonNull(t, "t");
|
||||||
|
synchronized (connectLock) {
|
||||||
|
this.serverFailure = t;
|
||||||
|
CompletableFuture<Void> f = this.serverReady;
|
||||||
|
if (!f.isDone()) f.completeExceptionally(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void awaitServerConnection() throws IOException {
|
||||||
|
ProtocolClient c = this.client;
|
||||||
|
if (c == null) {
|
||||||
|
throw new IllegalStateException("ProtocolClient not attached. Call OacWebRequestBroker.attachRuntime(...) during install.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.getClientServerConnection() != null && c.getClientServerConnection().isConnected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<Void> f = this.serverReady;
|
||||||
|
try {
|
||||||
|
f.get(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
throw new IOException("Timeout waiting for server connection after " + CONNECT_TIMEOUT_SECONDS + "s", e);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
Throwable cause = (e.getCause() == null) ? e : e.getCause();
|
||||||
|
throw new IOException("Server connection failed: " + cause.getMessage(), cause);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new IOException("Interrupted while waiting for server connection", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.getClientServerConnection() == null || !c.getClientServerConnection().isConnected()) {
|
||||||
|
Throwable fail = this.serverFailure;
|
||||||
|
if (fail != null) throw new IOException("Server connection failed: " + fail.getMessage(), fail);
|
||||||
|
throw new IOException("Server gate opened but connection is not connected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a resource request and blocks until completion.
|
||||||
|
*
|
||||||
|
* <p><b>Required flow:</b></p>
|
||||||
|
* <ol>
|
||||||
|
* <li>Trigger INS query for URL host (EVERY request).</li>
|
||||||
|
* <li>Wait for INS response -> connect -> server connected gate.</li>
|
||||||
|
* <li>Send WEB request packet.</li>
|
||||||
|
* </ol>
|
||||||
|
*/
|
||||||
|
public OacWebResponse fetch(
|
||||||
|
URL url,
|
||||||
|
String method,
|
||||||
|
Map<String, String> headers,
|
||||||
|
byte[] body,
|
||||||
|
long tabId,
|
||||||
|
long pageId,
|
||||||
|
long frameId, ProtocolBridge bridge
|
||||||
|
) throws IOException {
|
||||||
|
Objects.requireNonNull(url, "url");
|
||||||
|
|
||||||
|
String host = url.getHost();
|
||||||
|
if (host == null || host.isBlank()) {
|
||||||
|
throw new IOException("Missing InfoName in URL host: " + url);
|
||||||
|
}
|
||||||
|
|
||||||
|
InsResolutionCoordinator_v1_0_1_B coord = this.coordinator;
|
||||||
|
if (coord == null) {
|
||||||
|
throw new IllegalStateException("INS coordinator not attached. Ensure installer was called.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Requirement: trigger INS on EVERY request.
|
||||||
|
coord.requestResolve(host);
|
||||||
|
|
||||||
|
// Block until server is connected
|
||||||
|
awaitServerConnection();
|
||||||
|
|
||||||
|
ProtocolClient c = this.client;
|
||||||
|
if (c == null) {
|
||||||
|
throw new IllegalStateException("ProtocolClient not attached. Call OacWebRequestBroker.attachRuntime(...) during install.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String m = (method == null || method.isBlank()) ? "GET" : method.trim().toUpperCase(Locale.ROOT);
|
||||||
|
|
||||||
|
long requestId = requestCounter.getAndIncrement();
|
||||||
|
int flags = WebPacketFlags.RESOURCE;
|
||||||
|
|
||||||
|
WebPacketHeader header = new WebPacketHeader(
|
||||||
|
requestId,
|
||||||
|
tabId,
|
||||||
|
pageId,
|
||||||
|
frameId,
|
||||||
|
flags,
|
||||||
|
System.currentTimeMillis()
|
||||||
|
);
|
||||||
|
|
||||||
|
ResponseState st = new ResponseState(requestId);
|
||||||
|
inFlight.put(requestId, st);
|
||||||
|
|
||||||
|
Map<String, String> safeHeaders = (headers == null) ? Map.of() : new LinkedHashMap<>(headers);
|
||||||
|
byte[] safeBody = (body == null) ? new byte[0] : body;
|
||||||
|
WebResourceRequestPacket packet = new WebResourceRequestPacket(
|
||||||
|
header,
|
||||||
|
url.toString(),
|
||||||
|
m,
|
||||||
|
safeHeaders,
|
||||||
|
safeBody,
|
||||||
|
safeHeaders.get("content-type"),
|
||||||
|
null,
|
||||||
|
null, bridge
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (c.getClientServerConnection() == null || !c.getClientServerConnection().isConnected()) {
|
||||||
|
inFlight.remove(requestId);
|
||||||
|
throw new IOException("ServerConnection is not connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
c.getClientServerConnection().sendPacket(packet, TransportProtocol.TCP);
|
||||||
|
} catch (Exception e) {
|
||||||
|
inFlight.remove(requestId);
|
||||||
|
throw new IOException("Failed to send WebResourceRequestPacket", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return awaitAndBuildResponse(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String extractSession(Map<String, String> headers) {
|
||||||
|
return headerValue(headers, "session");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onResourceResponse(WebResourceResponsePacket p) {
|
||||||
|
if (p == null || p.getHeader() == null) return;
|
||||||
|
|
||||||
|
ResponseState st = inFlight.get(p.getHeader().getRequestId());
|
||||||
|
if (st == null) return;
|
||||||
|
|
||||||
|
synchronized (st.lock) {
|
||||||
|
if (st.completed) return;
|
||||||
|
|
||||||
|
st.statusCode = p.getStatusCode();
|
||||||
|
st.contentType = safeContentType(p.getContentType());
|
||||||
|
st.headers = safeHeaders(p.getHeaders());
|
||||||
|
|
||||||
|
boolean stream = false;
|
||||||
|
WebFlagInspector inspector = this.flagInspector;
|
||||||
|
if (inspector != null) {
|
||||||
|
stream = inspector.isStream(p.getHeader());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stream) {
|
||||||
|
byte[] b = (p.getBody() == null) ? new byte[0] : p.getBody();
|
||||||
|
st.memoryBody = b;
|
||||||
|
st.success = true;
|
||||||
|
st.completed = true;
|
||||||
|
st.done.countDown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
st.streamExpected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStreamStart(WebStreamStartPacket_v1_0_1_B p) {
|
||||||
|
if (p == null || p.getHeader() == null) return;
|
||||||
|
|
||||||
|
ResponseState st = inFlight.get(p.getHeader().getRequestId());
|
||||||
|
if (st == null) return;
|
||||||
|
|
||||||
|
synchronized (st.lock) {
|
||||||
|
if (st.completed) return;
|
||||||
|
|
||||||
|
st.statusCode = p.getStatusCode();
|
||||||
|
st.contentType = safeContentType(p.getContentType());
|
||||||
|
st.headers = safeHeaders(p.getHeaders());
|
||||||
|
st.totalLength = p.getTotalLength();
|
||||||
|
|
||||||
|
st.streamExpected = true;
|
||||||
|
|
||||||
|
if (st.spooler == null) {
|
||||||
|
try {
|
||||||
|
st.spooler = TempChunkSpooler.create(st.requestId);
|
||||||
|
st.spoolDir = st.spooler.dir();
|
||||||
|
} catch (IOException e) {
|
||||||
|
failLocked(st, "Failed to create stream spooler: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStreamChunk(WebStreamChunkPacket_v1_0_1_B p) {
|
||||||
|
if (p == null || p.getHeader() == null) return;
|
||||||
|
|
||||||
|
ResponseState st = inFlight.get(p.getHeader().getRequestId());
|
||||||
|
if (st == null) return;
|
||||||
|
|
||||||
|
int seq = p.getSeq();
|
||||||
|
if (seq < 0) return;
|
||||||
|
|
||||||
|
byte[] data = (p.getData() == null) ? new byte[0] : p.getData();
|
||||||
|
if (data.length == 0) return;
|
||||||
|
|
||||||
|
synchronized (st.lock) {
|
||||||
|
if (st.completed) return;
|
||||||
|
|
||||||
|
st.streamExpected = true;
|
||||||
|
|
||||||
|
if (st.spooler == null) {
|
||||||
|
try {
|
||||||
|
st.spooler = TempChunkSpooler.create(st.requestId);
|
||||||
|
st.spoolDir = st.spooler.dir();
|
||||||
|
} catch (IOException e) {
|
||||||
|
failLocked(st, "Failed to create stream spooler: " + e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
st.spooler.writeChunk(seq, data);
|
||||||
|
if (seq > st.maxSeqSeen) st.maxSeqSeen = seq;
|
||||||
|
} catch (IOException e) {
|
||||||
|
failLocked(st, "Failed to spool stream chunk: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] onStreamEndAndAssemble(WebStreamEndPacket_v1_0_1_B p) {
|
||||||
|
if (p == null || p.getHeader() == null) return null;
|
||||||
|
|
||||||
|
ResponseState st = inFlight.get(p.getHeader().getRequestId());
|
||||||
|
if (st == null) return null;
|
||||||
|
|
||||||
|
synchronized (st.lock) {
|
||||||
|
if (st.completed) return null;
|
||||||
|
|
||||||
|
st.success = p.isOk();
|
||||||
|
st.errorMessage = p.getError();
|
||||||
|
|
||||||
|
if (!st.success) {
|
||||||
|
st.completed = true;
|
||||||
|
st.done.countDown();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (st.spooler == null) {
|
||||||
|
st.finalFile = null;
|
||||||
|
st.completed = true;
|
||||||
|
st.done.countDown();
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
st.spoolDir = st.spooler.dir();
|
||||||
|
|
||||||
|
try {
|
||||||
|
st.finalFile = st.spooler.assembleBestEffort(st.maxSeqSeen);
|
||||||
|
} catch (IOException e) {
|
||||||
|
failLocked(st, "Failed to assemble stream: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
st.spooler.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
st.completed = true;
|
||||||
|
st.done.countDown();
|
||||||
|
|
||||||
|
if (st.finalFile == null) return new byte[0];
|
||||||
|
|
||||||
|
try (InputStream in = Files.newInputStream(st.finalFile)) {
|
||||||
|
return in.readAllBytes();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OacWebResponse awaitAndBuildResponse(ResponseState st) throws IOException {
|
||||||
|
try {
|
||||||
|
if (!st.done.await(RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
|
||||||
|
removeAndCleanup(st);
|
||||||
|
throw new IOException("Timeout waiting for web response (requestId=" + st.requestId + ")");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
removeAndCleanup(st);
|
||||||
|
throw new IOException("Interrupted while waiting for web response (requestId=" + st.requestId + ")", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
inFlight.remove(st.requestId);
|
||||||
|
|
||||||
|
synchronized (st.lock) {
|
||||||
|
if (!st.success) {
|
||||||
|
cleanupLocked(st);
|
||||||
|
String msg = (st.errorMessage == null || st.errorMessage.isBlank()) ? "Request failed" : st.errorMessage;
|
||||||
|
throw new IOException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!st.streamExpected && st.finalFile == null) {
|
||||||
|
byte[] b = (st.memoryBody == null) ? new byte[0] : st.memoryBody;
|
||||||
|
return new OacWebResponse(
|
||||||
|
st.statusCode,
|
||||||
|
safeContentType(st.contentType),
|
||||||
|
OacWebResponse.safeHeaders(st.headers),
|
||||||
|
new ByteArrayInputStream(b),
|
||||||
|
b.length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (st.finalFile == null) {
|
||||||
|
return new OacWebResponse(
|
||||||
|
st.statusCode,
|
||||||
|
safeContentType(st.contentType),
|
||||||
|
OacWebResponse.safeHeaders(st.headers),
|
||||||
|
new ByteArrayInputStream(new byte[0]),
|
||||||
|
0L
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream fileIn = Files.newInputStream(st.finalFile, StandardOpenOption.READ);
|
||||||
|
return new OacWebResponse(
|
||||||
|
st.statusCode,
|
||||||
|
safeContentType(st.contentType),
|
||||||
|
OacWebResponse.safeHeaders(st.headers),
|
||||||
|
new DeleteOnCloseInputStream(fileIn, st.finalFile, st.spoolDir),
|
||||||
|
safeFileSize(st.finalFile)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeAndCleanup(ResponseState st) {
|
||||||
|
inFlight.remove(st.requestId);
|
||||||
|
synchronized (st.lock) {
|
||||||
|
cleanupLocked(st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void cleanupLocked(ResponseState st) {
|
||||||
|
if (st.spooler != null) {
|
||||||
|
try {
|
||||||
|
st.spooler.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
st.spooler = null;
|
||||||
|
}
|
||||||
|
if (st.finalFile != null) {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(st.finalFile);
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
st.finalFile = null;
|
||||||
|
}
|
||||||
|
if (st.spoolDir != null) {
|
||||||
|
try {
|
||||||
|
if (Files.exists(st.spoolDir)) {
|
||||||
|
try (DirectoryStream<Path> ds = Files.newDirectoryStream(st.spoolDir)) {
|
||||||
|
for (Path p : ds) {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(p);
|
||||||
|
} catch (IOException ignored2) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(st.spoolDir);
|
||||||
|
} catch (IOException ignored2) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
st.spoolDir = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void failLocked(ResponseState st, String message) {
|
||||||
|
st.success = false;
|
||||||
|
st.errorMessage = message;
|
||||||
|
st.completed = true;
|
||||||
|
st.done.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long safeFileSize(Path p) {
|
||||||
|
try {
|
||||||
|
return Files.size(p);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return -1L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String safeContentType(String ct) {
|
||||||
|
return (ct == null || ct.isBlank()) ? "application/octet-stream" : ct;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, String> safeHeaders(Map<String, String> headers) {
|
||||||
|
return (headers == null || headers.isEmpty()) ? Map.of() : Map.copyOf(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String headerValue(Map<String, String> headers, String nameLower) {
|
||||||
|
if (headers == null || headers.isEmpty() || nameLower == null) return null;
|
||||||
|
String needle = nameLower.trim().toLowerCase(Locale.ROOT);
|
||||||
|
for (Map.Entry<String, String> e : headers.entrySet()) {
|
||||||
|
if (e.getKey() == null) continue;
|
||||||
|
if (e.getKey().trim().toLowerCase(Locale.ROOT).equals(needle)) {
|
||||||
|
return e.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ResponseState {
|
||||||
|
private final long requestId;
|
||||||
|
private final Object lock = new Object();
|
||||||
|
private final CountDownLatch done = new CountDownLatch(1);
|
||||||
|
|
||||||
|
private int statusCode = 0;
|
||||||
|
private String contentType = "application/octet-stream";
|
||||||
|
private Map<String, String> headers = Map.of();
|
||||||
|
|
||||||
|
private boolean streamExpected;
|
||||||
|
private long totalLength = -1L;
|
||||||
|
|
||||||
|
private int maxSeqSeen = -1;
|
||||||
|
|
||||||
|
private boolean completed;
|
||||||
|
private boolean success;
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
|
private byte[] memoryBody;
|
||||||
|
|
||||||
|
private TempChunkSpooler spooler;
|
||||||
|
private Path spoolDir;
|
||||||
|
private Path finalFile;
|
||||||
|
|
||||||
|
private ResponseState(long requestId) {
|
||||||
|
this.requestId = requestId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class TempChunkSpooler implements Closeable {
|
||||||
|
|
||||||
|
private final Path dir;
|
||||||
|
|
||||||
|
private TempChunkSpooler(Path dir) {
|
||||||
|
this.dir = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TempChunkSpooler create(long requestId) throws IOException {
|
||||||
|
Path dir = Files.createTempDirectory("oac-web-stream-" + requestId + "-");
|
||||||
|
return new TempChunkSpooler(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeChunk(int seq, byte[] data) throws IOException {
|
||||||
|
Path p = dir.resolve(seq + ".chunk");
|
||||||
|
Files.write(p, data, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
Path assembleBestEffort(int maxSeqSeen) throws IOException {
|
||||||
|
Path out = Files.createTempFile("oac-web-stream-final-", ".bin");
|
||||||
|
|
||||||
|
try (OutputStream os = Files.newOutputStream(out, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
|
||||||
|
for (int seq = 0; seq <= maxSeqSeen; seq++) {
|
||||||
|
Path p = dir.resolve(seq + ".chunk");
|
||||||
|
if (!Files.exists(p)) continue;
|
||||||
|
try (InputStream in = Files.newInputStream(p, StandardOpenOption.READ)) {
|
||||||
|
in.transferTo(os);
|
||||||
|
}
|
||||||
|
Files.deleteIfExists(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path dir() {
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (!Files.exists(dir)) return;
|
||||||
|
|
||||||
|
try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir)) {
|
||||||
|
for (Path p : ds) {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(p);
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(dir);
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a resolved web response for the JavaFX WebView.
|
||||||
|
*
|
||||||
|
* @param statusCode HTTP-like status code
|
||||||
|
* @param contentType response content-type
|
||||||
|
* @param headers response headers
|
||||||
|
* @param bodyStream body stream
|
||||||
|
* @param contentLength content length if known, else -1
|
||||||
|
*/
|
||||||
|
public record OacWebResponse(
|
||||||
|
int statusCode,
|
||||||
|
String contentType,
|
||||||
|
Map<String, String> headers,
|
||||||
|
InputStream bodyStream,
|
||||||
|
long contentLength
|
||||||
|
) {
|
||||||
|
public OacWebResponse {
|
||||||
|
Objects.requireNonNull(headers, "headers");
|
||||||
|
Objects.requireNonNull(bodyStream, "bodyStream");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> safeHeaders(Map<String, String> h) {
|
||||||
|
return (h == null) ? Collections.emptyMap() : Collections.unmodifiableMap(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
|
||||||
|
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketFlags;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interprets {@link WebPacketHeader#getFlags()} into semantic booleans.
|
||||||
|
*/
|
||||||
|
public interface WebFlagInspector {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param header web packet header
|
||||||
|
* @return true if the packet is part of a streamed body transfer
|
||||||
|
*/
|
||||||
|
boolean isStream(WebPacketHeader header);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation based on {@link WebPacketFlags#STREAM}.
|
||||||
|
*/
|
||||||
|
final class Default implements WebFlagInspector {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStream(WebPacketHeader header) {
|
||||||
|
Objects.requireNonNull(header, "header");
|
||||||
|
return WebPacketFlags.has(header.getFlags(), WebPacketFlags.STREAM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides per-request WEB correlation context (tab/page/frame).
|
||||||
|
*/
|
||||||
|
public interface WebRequestContextProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the context to use for this URL request.
|
||||||
|
*
|
||||||
|
* @param url requested URL
|
||||||
|
* @return context (never null)
|
||||||
|
*/
|
||||||
|
WebRequestContext contextFor(URL url);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable context DTO.
|
||||||
|
*
|
||||||
|
* @param tabId stable tab id
|
||||||
|
* @param pageId navigation instance id
|
||||||
|
* @param frameId frame id (0 = main frame)
|
||||||
|
*/
|
||||||
|
record WebRequestContext(long tabId, long pageId, long frameId) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default context provider (tab/page/frame = 0).
|
||||||
|
*/
|
||||||
|
final class Default implements WebRequestContextProvider {
|
||||||
|
@Override
|
||||||
|
public WebRequestContext contextFor(URL url) {
|
||||||
|
return new WebRequestContext(0L, 0L, 0L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,14 +12,20 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public enum ProtocolVersion implements Serializable {
|
public enum ProtocolVersion implements Serializable {
|
||||||
/**
|
/**
|
||||||
* Support for old OAC-Project => <a href="https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/">*_old</a>
|
* For classic OAC-Project => <a href="https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/">"classic"-branch</a>
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
|
||||||
PV_1_0_0_CLASSIC("1.0.0", ProtocolType.CLASSIC, ProtocolSide.WEB_INS, List.of(Protocol.HTTP)),
|
PV_1_0_0_CLASSIC("1.0.0", ProtocolType.CLASSIC, ProtocolSide.WEB_INS, List.of(Protocol.HTTP)),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* First Beta Version of OAC-Protocol
|
* Version {@link ProtocolVersion#PV_1_0_0_BETA} has many bugs and does not work as expected (occurred by incompleted packets).
|
||||||
|
* Use {@link ProtocolVersion#PV_1_0_1_BETA} or newer.
|
||||||
*/
|
*/
|
||||||
PV_1_0_0_BETA("1.0.0", ProtocolType.BETA, ProtocolSide.ALL, List.of(Protocol.OAC));
|
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
|
||||||
|
PV_1_0_0_BETA("1.0.0", ProtocolType.BETA, ProtocolSide.ALL, List.of(Protocol.OAC)),
|
||||||
|
|
||||||
|
PV_1_0_1_BETA("1.0.1", ProtocolType.BETA, ProtocolSide.ALL, List.of(Protocol.OAC), PV_1_0_0_BETA),
|
||||||
|
;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version string of the protocol version.
|
* The version string of the protocol version.
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
|
||||||
|
|
||||||
|
import org.openautonomousconnection.protocol.side.ins.ProtocolINSServer;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods for working with {@link INSRecord} instances.
|
||||||
|
* <p>
|
||||||
|
* Features:
|
||||||
|
* <ul>
|
||||||
|
* <li>Recursive CNAME resolution with loop protection</li>
|
||||||
|
* <li>Basic record validation helpers</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class INSRecordTools {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of CNAME hops before resolution is aborted.
|
||||||
|
*/
|
||||||
|
public static final int MAX_CNAME_DEPTH = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves records for the given name and type, following CNAME chains if necessary.
|
||||||
|
* <p>
|
||||||
|
* Resolution strategy:
|
||||||
|
* <ol>
|
||||||
|
* <li>Try to resolve the requested {@code type} directly via {@link ProtocolINSServer#resolve}</li>
|
||||||
|
* <li>If no records are found:
|
||||||
|
* <ul>
|
||||||
|
* <li>Resolve {@link INSRecordType#CNAME} for the same (tln, name, sub)</li>
|
||||||
|
* <li>For each CNAME target, follow the chain up to {@link #MAX_CNAME_DEPTH}</li>
|
||||||
|
* </ul>
|
||||||
|
* </li>
|
||||||
|
* <li>Return the first non-empty resolution result</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* @param server The INS server implementation to use for lookups.
|
||||||
|
* @param tln Top-level-name.
|
||||||
|
* @param name InfoName.
|
||||||
|
* @param sub Optional sub-name, may be {@code null}.
|
||||||
|
* @param type Desired record type.
|
||||||
|
* @return A list of resolved records. May be empty if nothing could be resolved.
|
||||||
|
*/
|
||||||
|
public static List<INSRecord> resolveWithCNAME(ProtocolINSServer server,
|
||||||
|
String tln,
|
||||||
|
String name,
|
||||||
|
String sub,
|
||||||
|
INSRecordType type) {
|
||||||
|
|
||||||
|
// If we explicitly ask for CNAME, just return raw CNAME records
|
||||||
|
if (type == INSRecordType.CNAME) return server.resolve(tln, name, sub, INSRecordType.CNAME);
|
||||||
|
|
||||||
|
// Try direct resolution first
|
||||||
|
List<INSRecord> direct = server.resolve(tln, name, sub, type);
|
||||||
|
if (!direct.isEmpty()) return direct;
|
||||||
|
|
||||||
|
// Otherwise follow CNAME chain
|
||||||
|
Set<String> visited = new HashSet<>();
|
||||||
|
return followCNAME(server, tln, name, sub, type, 0, visited);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<INSRecord> followCNAME(ProtocolINSServer server, String tln, String name, String sub, INSRecordType targetType, int depth, Set<String> visited) {
|
||||||
|
|
||||||
|
if (depth > MAX_CNAME_DEPTH) {
|
||||||
|
server.getProtocolBridge().getProtocolValues().logger.warn("Max CNAME depth exceeded for " + fqdn(tln, name, sub));
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
String key = fqdn(tln, name, sub);
|
||||||
|
if (!visited.add(key)) {
|
||||||
|
// Loop detected
|
||||||
|
server.getProtocolBridge().getProtocolValues().logger.warn("CNAME loop detected for " + key);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<INSRecord> cnames = server.resolve(tln, name, sub, INSRecordType.CNAME);
|
||||||
|
if (cnames.isEmpty()) return Collections.emptyList();
|
||||||
|
|
||||||
|
for (INSRecord cname : cnames) {
|
||||||
|
if (!isValidRecord(cname)) continue;
|
||||||
|
|
||||||
|
// Target might be "host.tln" or full "sub.name.tln"
|
||||||
|
ParsedName target = parseTargetName(cname.value);
|
||||||
|
if (target == null) continue;
|
||||||
|
|
||||||
|
List<INSRecord> resolved = server.resolve(target.tln, target.name, target.sub, targetType);
|
||||||
|
|
||||||
|
if (!resolved.isEmpty()) return resolved;
|
||||||
|
|
||||||
|
// Try next CNAME hop
|
||||||
|
List<INSRecord> deeper = followCNAME(server, target.tln, target.name, target.sub, targetType, depth + 1, visited);
|
||||||
|
|
||||||
|
if (!deeper.isEmpty()) return deeper;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String fqdn(String tln, String name, String sub) {
|
||||||
|
if (sub == null || sub.isEmpty()) return name + "." + tln;
|
||||||
|
return sub + "." + name + "." + tln;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a CNAME target into (tln, name, sub) assuming the last label is the TLN
|
||||||
|
* and the one before that is the InfoName.
|
||||||
|
*
|
||||||
|
* @param target FQDN string.
|
||||||
|
* @return ParsedName or {@code null} if it cannot be parsed.
|
||||||
|
*/
|
||||||
|
private static ParsedName parseTargetName(String target) {
|
||||||
|
if (target == null) return null;
|
||||||
|
|
||||||
|
String hostname = target.trim();
|
||||||
|
if (hostname.endsWith(".")) hostname = hostname.substring(0, hostname.length() - 1);
|
||||||
|
|
||||||
|
String[] parts = hostname.split("\\.");
|
||||||
|
if (parts.length < 2) return null;
|
||||||
|
|
||||||
|
String tln = parts[parts.length - 1];
|
||||||
|
String name = parts[parts.length - 2];
|
||||||
|
String sub = null;
|
||||||
|
|
||||||
|
if (parts.length > 2) sub = String.join(".", Arrays.copyOfRange(parts, 0, parts.length - 2));
|
||||||
|
return new ParsedName(tln, name, sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs basic validation on a single {@link INSRecord}.
|
||||||
|
* <p>
|
||||||
|
* Checks:
|
||||||
|
* <ul>
|
||||||
|
* <li>type is non-null</li>
|
||||||
|
* <li>value is non-null and non-empty</li>
|
||||||
|
* <li>port is in range 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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.string.RandomString;
|
||||||
|
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.ProtocolCustomServer;
|
||||||
|
import org.openautonomousconnection.protocol.side.web.managers.AuthManager;
|
||||||
|
import org.openautonomousconnection.protocol.side.web.managers.RuleManager;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the web server for the protocol.
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
|
||||||
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
||||||
|
public abstract class ProtocolWebServer_1_0_0_B extends ProtocolCustomServer {
|
||||||
|
/**
|
||||||
|
* Folder for web content.
|
||||||
|
*/
|
||||||
|
private final File contentFolder;
|
||||||
|
/**
|
||||||
|
* Folder for error pages.
|
||||||
|
*/
|
||||||
|
private final File errorsFolder;
|
||||||
|
/**
|
||||||
|
* A unique secret for session management.
|
||||||
|
*/
|
||||||
|
private final String uniqueSessionString;
|
||||||
|
/**
|
||||||
|
* The expiration time of a Session in minutes
|
||||||
|
*/
|
||||||
|
private final int sessionExpire;
|
||||||
|
/**
|
||||||
|
* The max upload size in MB
|
||||||
|
*/
|
||||||
|
private final int maxUploadSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the web server with the given configuration, authentication, and rules files.
|
||||||
|
*
|
||||||
|
* @param authFile The authentication file.
|
||||||
|
* @param rulesFile The rules file.
|
||||||
|
* @param sessionExpire The expiration time of a Session in minutes
|
||||||
|
* @param uploadSize The max upload size in MB
|
||||||
|
* @throws Exception If an error occurs during initialization.
|
||||||
|
*/
|
||||||
|
public ProtocolWebServer_1_0_0_B(File authFile, File rulesFile, int sessionExpire, int uploadSize) throws Exception {
|
||||||
|
super("server", "server");
|
||||||
|
|
||||||
|
this.sessionExpire = sessionExpire;
|
||||||
|
this.maxUploadSize = uploadSize;
|
||||||
|
|
||||||
|
// Set up content and error folders
|
||||||
|
contentFolder = new File("content");
|
||||||
|
errorsFolder = new File("errors");
|
||||||
|
|
||||||
|
// Create folders if they don't exist
|
||||||
|
if (!contentFolder.exists()) contentFolder.mkdir();
|
||||||
|
if (!errorsFolder.exists()) errorsFolder.mkdir();
|
||||||
|
|
||||||
|
// Create auth and rules files with default content if they don't exist
|
||||||
|
if (!authFile.exists()) {
|
||||||
|
authFile.createNewFile();
|
||||||
|
FileUtils.writeFile(authFile, """
|
||||||
|
admin:5e884898da28047151d0e56f8dc6292773603d0d6aabbddab8f91d8e5f99f6c7
|
||||||
|
user:e99a18c428cb38d5f260853678922e03abd8335f
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create default rules file if it doesn't exist
|
||||||
|
if (!rulesFile.exists()) {
|
||||||
|
rulesFile.createNewFile();
|
||||||
|
FileUtils.writeFile(rulesFile, """
|
||||||
|
{
|
||||||
|
"allow": [
|
||||||
|
"index.html",
|
||||||
|
"css/*",
|
||||||
|
"private/info.php"
|
||||||
|
],
|
||||||
|
"deny": [
|
||||||
|
"private/*"
|
||||||
|
],
|
||||||
|
"auth": [
|
||||||
|
"private/*",
|
||||||
|
"admin/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load authentication and rules
|
||||||
|
uniqueSessionString = AuthManager.sha256(new RandomString(new Random(System.currentTimeMillis()).nextInt(10, 20)).nextString());
|
||||||
|
|
||||||
|
AuthManager.loadAuthFile(authFile);
|
||||||
|
RuleManager.loadRules(rulesFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final File getContentFolder() {
|
||||||
|
return contentFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final File getErrorsFolder() {
|
||||||
|
return errorsFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String getUniqueSessionString() {
|
||||||
|
return uniqueSessionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getSessionExpire() {
|
||||||
|
return sessionExpire;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getMaxUploadSize() {
|
||||||
|
return maxUploadSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the server receives a WebRequestPacket from the client.
|
||||||
|
*
|
||||||
|
* @param client The connected web client (pipeline + web socket).
|
||||||
|
* @param request The full decoded request packet.
|
||||||
|
* @return The response packet that should be sent back to the client.
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
|
||||||
|
public abstract WebResponsePacket onWebRequest(CustomConnectedClient client, WebRequestPacket request);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
|
||||||
|
|
||||||
|
public enum WebRequestMethod {
|
||||||
|
GET, POST, EXECUTE, SCRIPT
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils;
|
package org.openautonomousconnection.protocol.versions.v1_0_0.classic;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum representing the protocol versions for the Classic protocol.
|
* Enum representing the protocol versions for the Classic protocol.
|
||||||
*/
|
*/
|
||||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
@Deprecated(forRemoval = true, since = "1.0.1-BETA.0.1")
|
||||||
public enum Classic_ProtocolVersion implements Serializable {
|
public enum Classic_ProtocolVersion implements Serializable {
|
||||||
PV_1_0_0("1.0.0");
|
PV_1_0_0("1.0.0");
|
||||||
public final String version;
|
public final String version;
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.events;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerINSServer;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_RequestDomain;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This event is fired when a classic domain packet is received.
|
|
||||||
* This event is deprecated and will be marked for removal in future versions.
|
|
||||||
*
|
|
||||||
* @see ClassicHandlerINSServer
|
|
||||||
* @see org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerClient
|
|
||||||
* @see org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerWebServer
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
|
||||||
public final class Classic_DomainPacketReceivedEvent extends Event {
|
|
||||||
|
|
||||||
public final Classic_ProtocolVersion protocolVersion;
|
|
||||||
public final Classic_Domain domain;
|
|
||||||
public final Classic_RequestDomain requestDomain;
|
|
||||||
public final int clientID;
|
|
||||||
|
|
||||||
public Classic_DomainPacketReceivedEvent(Classic_ProtocolVersion protocolVersion, Classic_Domain domain, Classic_RequestDomain requestDomain, int clientID) {
|
|
||||||
this.protocolVersion = protocolVersion;
|
|
||||||
this.domain = domain;
|
|
||||||
this.requestDomain = requestDomain;
|
|
||||||
this.clientID = clientID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Object clone() throws CloneNotSupportedException {
|
|
||||||
return super.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
return super.equals(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return super.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return super.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.events;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerINSServer;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_RequestDomain;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This event is fired when a classic ping packet is received.
|
|
||||||
* This event is deprecated and will be marked for removal in future versions.
|
|
||||||
*
|
|
||||||
* @see ClassicHandlerINSServer
|
|
||||||
* @see org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerClient
|
|
||||||
* @see org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerWebServer
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
|
||||||
public final class Classic_PingPacketReceivedEvent extends Event {
|
|
||||||
public final Classic_ProtocolVersion protocolVersion;
|
|
||||||
public final Classic_Domain domain;
|
|
||||||
public final Classic_RequestDomain requestDomain;
|
|
||||||
public final boolean reachable;
|
|
||||||
public final int clientID;
|
|
||||||
|
|
||||||
public Classic_PingPacketReceivedEvent(Classic_ProtocolVersion protocolVersion, Classic_Domain domain, Classic_RequestDomain requestDomain, boolean reachable, int clientID) {
|
|
||||||
this.protocolVersion = protocolVersion;
|
|
||||||
this.domain = domain;
|
|
||||||
this.requestDomain = requestDomain;
|
|
||||||
this.reachable = reachable;
|
|
||||||
this.clientID = clientID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Object clone() throws CloneNotSupportedException {
|
|
||||||
return super.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
return super.equals(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return super.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return super.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.classic.Classic_MessagePacket;
|
|
||||||
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
|
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.site.Classic_SiteType;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract class defining the client-side handler for Classic protocol operations.
|
|
||||||
*/
|
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.CLIENT)
|
|
||||||
public abstract class ClassicHandlerClient {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference to the ProtocolClient
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private final ProtocolClient client;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the client variable
|
|
||||||
*
|
|
||||||
* @param client The ProtocolClient Object
|
|
||||||
*/
|
|
||||||
public ClassicHandlerClient(ProtocolClient client) {
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void unsupportedClassicPacket(String classicPacketClassName, Object[] content);
|
|
||||||
|
|
||||||
public abstract void handleHTMLContent(Classic_SiteType siteType, Classic_Domain domain, String html);
|
|
||||||
|
|
||||||
public abstract void handleMessage(String message, Classic_ProtocolVersion protocolVersion);
|
|
||||||
|
|
||||||
public final void sendMessage(String message) throws IOException, ClassNotFoundException {
|
|
||||||
client.getClientINSConnection().sendPacket(new Classic_MessagePacket(message, 0, client.getProtocolBridge()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void validationCompleted(Classic_Domain domain, INSResponseStatus insResponseStatus);
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers;
|
|
||||||
|
|
||||||
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
|
||||||
import org.openautonomousconnection.protocol.side.ins.ConnectedProtocolClient;
|
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_RequestDomain;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
|
|
||||||
|
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract class defining the INS server-side handler for Classic protocol operations.
|
|
||||||
*/
|
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
|
|
||||||
public abstract class ClassicHandlerINSServer {
|
|
||||||
public abstract void handleMessage(ConnectedProtocolClient client, String message, Classic_ProtocolVersion protocolVersion);
|
|
||||||
|
|
||||||
public abstract Classic_Domain getDomain(Classic_RequestDomain requestDomain) throws SQLException;
|
|
||||||
|
|
||||||
public abstract Classic_Domain ping(Classic_RequestDomain requestDomain) throws SQLException;
|
|
||||||
|
|
||||||
public abstract void unsupportedClassicPacket(String className, Object[] content, ConnectedProtocolClient client);
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers;
|
|
||||||
|
|
||||||
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
|
||||||
import org.openautonomousconnection.protocol.side.ins.ConnectedProtocolClient;
|
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract class defining the web server-side handler for Classic protocol operations.
|
|
||||||
*/
|
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
|
||||||
public abstract class ClassicHandlerWebServer {
|
|
||||||
public abstract void handleMessage(ConnectedProtocolClient client, String message, Classic_ProtocolVersion protocolVersion);
|
|
||||||
|
|
||||||
public abstract void unsupportedClassicPacket(String className, Object[] content, ConnectedProtocolClient client);
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Classic_Domain is an old representation of a InfoName, maintained for backward compatibility.
|
|
||||||
* It encapsulates the InfoName's name, top-level name, path, and destination.
|
|
||||||
* This class is deprecated and users are encouraged to use the InfoName class instead.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
|
||||||
public class Classic_Domain implements Serializable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the domain.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
|
||||||
public final String name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The top-level domain.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
|
||||||
public final String topLevelDomain;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The path component of the domain.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
|
||||||
public final String path;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The destination of the domain, typically the full URL or address.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
|
||||||
public final String destination;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ProtocolBridge reference.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
|
||||||
public final ProtocolBridge protocolBridge;
|
|
||||||
|
|
||||||
public Classic_Domain(String name, String topLevelDomain, String destination, String path, ProtocolBridge bridge) {
|
|
||||||
this.protocolBridge = bridge;
|
|
||||||
this.name = name;
|
|
||||||
this.topLevelDomain = topLevelDomain;
|
|
||||||
this.destination = destination;
|
|
||||||
this.path = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final Object clone() throws CloneNotSupportedException {
|
|
||||||
return new Classic_Domain(name, topLevelDomain, destination, path, protocolBridge);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final boolean equals(Object obj) {
|
|
||||||
if (!(obj instanceof Classic_Domain other)) return false;
|
|
||||||
return other.name.equalsIgnoreCase(name) && other.topLevelDomain.equalsIgnoreCase(topLevelDomain);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final int hashCode() {
|
|
||||||
return super.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects;
|
|
||||||
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class representing a local domain in the Classic protocol.
|
|
||||||
* This class extends Classic_Domain and is used for local domain representation.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
|
||||||
public final class Classic_LocalDomain extends Classic_Domain {
|
|
||||||
public Classic_LocalDomain(String name, String endName, String path, ProtocolBridge protocolBridge) {
|
|
||||||
super(name, endName, null, path, protocolBridge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects;
|
|
||||||
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class representing a request for a domain in the Classic protocol.
|
|
||||||
* This class extends Classic_Domain and is used for requesting domain information.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
|
||||||
public final class Classic_RequestDomain extends Classic_Domain implements Serializable {
|
|
||||||
|
|
||||||
public Classic_RequestDomain(String name, String topLevelDomain, String path, ProtocolBridge protocolBridge) {
|
|
||||||
super(name, topLevelDomain, null, path, protocolBridge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.site;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum representing different types of sites in the Classic protocol.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
|
||||||
public enum Classic_SiteType implements Serializable {
|
|
||||||
/**
|
|
||||||
* Client site type.
|
|
||||||
*/
|
|
||||||
CLIENT("oac-client"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Web server site type.
|
|
||||||
*/
|
|
||||||
SERVER("oac-server"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* INS server site type.
|
|
||||||
*/
|
|
||||||
PUBLIC("oac"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Protocol site type.
|
|
||||||
*/
|
|
||||||
PROTOCOL("oac-protocol"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Local site type.
|
|
||||||
*/
|
|
||||||
LOCAL("oac-local");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the site type.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
|
||||||
public final String name;
|
|
||||||
|
|
||||||
Classic_SiteType(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.site;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class contains predefined HTML content for various website responses in the Classic protocol.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
|
||||||
public final class Classic_WebsitesContent extends DefaultMethodsOverrider {
|
|
||||||
|
|
||||||
public static final String DOMAIN_NOT_FOUND = """
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>404 - Domain not found</title>
|
|
||||||
<meta content="UTF-8" name="charset"/>
|
|
||||||
<meta content="Open Autonomous Connection" name="author"/>
|
|
||||||
<meta content="Domain not found" name="description"/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>404 - This infoName was not found</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
""";
|
|
||||||
|
|
||||||
public static final String FILE_NOT_FOUND = """
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>404 - File not found</title>
|
|
||||||
<meta content="UTF-8" name="charset"/>
|
|
||||||
<meta content="Open Autonomous Connection" name="author"/>
|
|
||||||
<meta content="File not found" name="description"/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>404 - This file was not found</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
""";
|
|
||||||
|
|
||||||
public static final String DOMAIN_NOT_REACHABLE = """
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>504 - Site not reachable</title>
|
|
||||||
<meta content="UTF-8" name="charset"/>
|
|
||||||
<meta content="Open Autonomous Connection" name="author"/>
|
|
||||||
<meta content="Site not reached" name="description"/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>504 - This site is currently not reachable</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
""";
|
|
||||||
public static String ERROR_OCCURRED = ERROR_OCCURRED("No specified details!");
|
|
||||||
|
|
||||||
public static String ERROR_OCCURRED(String errorDetails) {
|
|
||||||
return """
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>500 - Error occurred</title>
|
|
||||||
<meta content="UTF-8" name="charset"/>
|
|
||||||
<meta content="Open Autonomous Connection" name="author"/>
|
|
||||||
<meta content="Site not reached" name="description"/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>500 - Error occured while resolving infoName!</h1>
|
|
||||||
<h4>Details:</h2>
|
|
||||||
<h5>""" + errorDetails + "</h5>" + """
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.classic.Classic_PingPacket;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.events.Classic_DomainPacketReceivedEvent;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.events.Classic_PingPacketReceivedEvent;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_LocalDomain;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.site.Classic_SiteType;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.site.Classic_WebsitesContent;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class listens for events related to Classic protocol operations on the client side.
|
|
||||||
* It handles domain resolution and ping responses, facilitating communication with the INS server
|
|
||||||
* and web content retrieval.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
|
||||||
public final class Classic_ClientListener extends EventListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference to the ProtocolBridge
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private ProtocolBridge protocolBridge;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set protocol bridge
|
|
||||||
*
|
|
||||||
* @param protocolBridge The ProtocolBridge object
|
|
||||||
*/
|
|
||||||
public void setProtocolBridge(ProtocolBridge protocolBridge) {
|
|
||||||
if (this.protocolBridge != null) return;
|
|
||||||
this.protocolBridge = protocolBridge;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the event when a domain packet is received.
|
|
||||||
* It checks if the domain exists and sends a ping request to the INS server.
|
|
||||||
* If the domain does not exist, it handles the error accordingly.
|
|
||||||
*
|
|
||||||
* @param event The event containing domain information.
|
|
||||||
*/
|
|
||||||
@Listener
|
|
||||||
public void onDomain(Classic_DomainPacketReceivedEvent event) {
|
|
||||||
// Check if the domain exists
|
|
||||||
boolean exists = event.domain != null;
|
|
||||||
|
|
||||||
if (exists) {
|
|
||||||
try {
|
|
||||||
// Send a ping request to the INS server
|
|
||||||
if (!protocolBridge.getProtocolClient().getClientINSConnection().sendPacket(new Classic_PingPacket(event.requestDomain, event.domain, false, protocolBridge))) {
|
|
||||||
// If sending the packet fails, handle the error
|
|
||||||
protocolBridge.getClassicHandlerClient().handleHTMLContent(Classic_SiteType.PROTOCOL, new Classic_LocalDomain("error-occurred", "html", "", protocolBridge),
|
|
||||||
Classic_WebsitesContent.ERROR_OCCURRED(event.domain + "/" + event.domain.path));
|
|
||||||
}
|
|
||||||
} catch (IOException | ClassNotFoundException e) {
|
|
||||||
// Handle any exceptions that occur during the process
|
|
||||||
protocolBridge.getClassicHandlerClient().handleHTMLContent(Classic_SiteType.PROTOCOL, new Classic_LocalDomain("error-occurred", "html", "", protocolBridge),
|
|
||||||
Classic_WebsitesContent.ERROR_OCCURRED(event.domain + "/" + event.domain.path + ":\n" + e.getMessage()));
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
// If the domain does not exist, handle the error
|
|
||||||
protocolBridge.getClassicHandlerClient().handleHTMLContent(Classic_SiteType.PROTOCOL, new Classic_LocalDomain("domain-not-found", "html", "", protocolBridge), Classic_WebsitesContent.DOMAIN_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the event when a ping packet is received.
|
|
||||||
* If the domain is reachable, it fetches the HTML content from the domain.
|
|
||||||
* If not reachable, it handles the error accordingly.
|
|
||||||
*
|
|
||||||
* @param event The event containing ping response information.
|
|
||||||
*/
|
|
||||||
@Listener
|
|
||||||
public void onPing(Classic_PingPacketReceivedEvent event) {
|
|
||||||
// If the domain is reachable, fetch the HTML content
|
|
||||||
if (event.reachable) {
|
|
||||||
String destination = event.domain.destination;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Create a URL object
|
|
||||||
URL url = new URL(destination);
|
|
||||||
HttpURLConnection connection2 = (HttpURLConnection) url.openConnection();
|
|
||||||
connection2.setRequestMethod("GET");
|
|
||||||
|
|
||||||
// Read the response
|
|
||||||
StringBuilder content = new StringBuilder();
|
|
||||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection2.getInputStream()))) {
|
|
||||||
String line;
|
|
||||||
while ((line = reader.readLine()) != null) content.append(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the HTML content
|
|
||||||
protocolBridge.getClassicHandlerClient().handleHTMLContent(Classic_SiteType.PUBLIC, event.domain, content.toString());
|
|
||||||
} catch (IOException exception) {
|
|
||||||
// Handle any exceptions that occur during the process
|
|
||||||
protocolBridge.getClassicHandlerClient().handleHTMLContent(Classic_SiteType.PROTOCOL, new Classic_LocalDomain("error-occurred", "html", "", protocolBridge),
|
|
||||||
Classic_WebsitesContent.ERROR_OCCURRED(exception.getMessage().replace(event.domain.destination, event.domain + "/" + event.domain.path)));
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
// If the domain is not reachable, handle the error
|
|
||||||
protocolBridge.getClassicHandlerClient().handleHTMLContent(Classic_SiteType.PROTOCOL, new Classic_LocalDomain("error-not-reached", "html", "", protocolBridge), Classic_WebsitesContent.DOMAIN_NOT_REACHABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Object clone() throws CloneNotSupportedException {
|
|
||||||
return super.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
return super.equals(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return super.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return super.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
|
|
||||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.site.Classic_SiteType;
|
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class for domain-related operations in the Classic protocol.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
|
||||||
class Classic_DomainUtils extends DefaultMethodsOverrider {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the top-level domain (TLD) from a given URL.
|
|
||||||
*
|
|
||||||
* @param url The URL from which to extract the TLD.
|
|
||||||
* @return The top-level domain as a string.
|
|
||||||
* @throws MalformedURLException If the URL is malformed.
|
|
||||||
*/
|
|
||||||
public static String getTopLevelDomain(String url) throws MalformedURLException {
|
|
||||||
URL uri = null;
|
|
||||||
String tldString = null;
|
|
||||||
|
|
||||||
if (url.startsWith(Classic_SiteType.PUBLIC.name + "://"))
|
|
||||||
url = url.substring((Classic_SiteType.PUBLIC.name + "://").length());
|
|
||||||
if (url.startsWith(Classic_SiteType.CLIENT.name + "://"))
|
|
||||||
url = url.substring((Classic_SiteType.CLIENT.name + "://").length());
|
|
||||||
if (url.startsWith(Classic_SiteType.SERVER.name + "://"))
|
|
||||||
url = url.substring((Classic_SiteType.SERVER.name + "://").length());
|
|
||||||
if (url.startsWith(Classic_SiteType.PROTOCOL.name + "://"))
|
|
||||||
url = url.substring((Classic_SiteType.PROTOCOL.name + "://").length());
|
|
||||||
if (url.startsWith(Classic_SiteType.LOCAL.name + "://"))
|
|
||||||
url = url.substring((Classic_SiteType.LOCAL.name + "://").length());
|
|
||||||
|
|
||||||
if (!url.startsWith("https://") && !url.startsWith("http://")) url = "https://" + url;
|
|
||||||
|
|
||||||
uri = new URL(url);
|
|
||||||
String[] domainNameParts = uri.getHost().split("\\.");
|
|
||||||
tldString = domainNameParts[domainNameParts.length - 1];
|
|
||||||
|
|
||||||
return tldString;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the domain name (excluding the TLD) from a given URL.
|
|
||||||
*
|
|
||||||
* @param url The URL from which to extract the domain name.
|
|
||||||
* @return The domain name as a string.
|
|
||||||
* @throws URISyntaxException If the URL syntax is incorrect.
|
|
||||||
* @throws MalformedURLException If the URL is malformed.
|
|
||||||
*/
|
|
||||||
public static String getDomainName(String url) throws URISyntaxException, MalformedURLException {
|
|
||||||
if (url.startsWith(Classic_SiteType.PUBLIC.name + "://"))
|
|
||||||
url = url.substring((Classic_SiteType.PUBLIC.name + "://").length());
|
|
||||||
if (url.startsWith(Classic_SiteType.CLIENT.name + "://"))
|
|
||||||
url = url.substring((Classic_SiteType.CLIENT.name + "://").length());
|
|
||||||
if (url.startsWith(Classic_SiteType.SERVER.name + "://"))
|
|
||||||
url = url.substring((Classic_SiteType.SERVER.name + "://").length());
|
|
||||||
if (url.startsWith(Classic_SiteType.PROTOCOL.name + "://"))
|
|
||||||
url = url.substring((Classic_SiteType.PROTOCOL.name + "://").length());
|
|
||||||
if (url.startsWith(Classic_SiteType.LOCAL.name + "://"))
|
|
||||||
url = url.substring((Classic_SiteType.LOCAL.name + "://").length());
|
|
||||||
|
|
||||||
if (!url.startsWith("https://") && !url.startsWith("http://")) url = "https://" + url;
|
|
||||||
|
|
||||||
URI uri = new URI(url);
|
|
||||||
return uri.getHost().replace("." + getTopLevelDomain(url), "");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the path component from a given URL.
|
|
||||||
*
|
|
||||||
* @param url The URL from which to extract the path.
|
|
||||||
* @return The path as a string.
|
|
||||||
*/
|
|
||||||
public static String getPath(String url) {
|
|
||||||
if (!url.startsWith(Classic_SiteType.PUBLIC.name + "://") && !url.startsWith(Classic_SiteType.CLIENT.name + "://") &&
|
|
||||||
!url.startsWith(Classic_SiteType.SERVER.name + "://") && !url.startsWith(Classic_SiteType.PROTOCOL.name + "://") &&
|
|
||||||
!url.startsWith(Classic_SiteType.LOCAL.name + "://") && !url.startsWith("http") && !url.startsWith("https")) {
|
|
||||||
url = Classic_SiteType.PUBLIC.name + "://" + url;
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] split = url.split("/");
|
|
||||||
if (split.length <= 3) return "";
|
|
||||||
|
|
||||||
StringBuilder path = new StringBuilder();
|
|
||||||
|
|
||||||
for (int i = 3; i < split.length; i++) path.append(split[i]).append("/");
|
|
||||||
|
|
||||||
String pathStr = path.toString();
|
|
||||||
if (pathStr.startsWith("/")) pathStr = pathStr.substring("/".length());
|
|
||||||
if (pathStr.endsWith("/")) pathStr = pathStr.substring(0, pathStr.length() - "/".length());
|
|
||||||
|
|
||||||
return pathStr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parsed INS address components.
|
||||||
|
*
|
||||||
|
* <p>Supported formats:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code name.tln}</li>
|
||||||
|
* <li>{@code sub.name.tln}</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public record InsParts(String tln, String name, String sub) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an InfoName into INS query parts.
|
||||||
|
*
|
||||||
|
* @param infoName InfoName host string
|
||||||
|
* @return parsed parts
|
||||||
|
* @throws IllegalArgumentException if format is invalid
|
||||||
|
*/
|
||||||
|
public static InsParts parse(String infoName) {
|
||||||
|
String in = Objects.requireNonNull(infoName, "infoName").trim();
|
||||||
|
if (in.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("InfoName is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] parts = in.split("\\.");
|
||||||
|
if (parts.length < 2 || parts.length > 3) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Invalid INS address format: " + infoName + " (expected name.tln or sub.name.tln)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String tln = parts[parts.length - 1].trim();
|
||||||
|
String name = parts[parts.length - 2].trim();
|
||||||
|
String sub = (parts.length == 3) ? parts[0].trim() : null;
|
||||||
|
|
||||||
|
if (tln.isEmpty() || name.isEmpty() || (sub != null && sub.isEmpty())) {
|
||||||
|
throw new IllegalArgumentException("Invalid INS address parts in: " + infoName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new InsParts(tln, name, sub);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache behavior for navigation/resource requests.
|
||||||
|
*/
|
||||||
|
public enum WebCacheMode {
|
||||||
|
DEFAULT,
|
||||||
|
BYPASS,
|
||||||
|
ONLY_CACHE
|
||||||
|
}
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
|
||||||
|
|
||||||
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamChunkPacket_v1_0_0_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamEndPacket_v1_0_0_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamStartPacket_v1_0_0_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamChunkPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamEndPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamStartPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compatibility mapper between Web protocol v1.0.0
|
||||||
|
* and Web protocol v1.0.1 (beta).
|
||||||
|
*
|
||||||
|
* <p>This class provides pure mapping logic and contains no networking code.</p>
|
||||||
|
*
|
||||||
|
* <p>Usage:
|
||||||
|
* <ul>
|
||||||
|
* <li>Server-side: map 1.0.0 {@link WebRequestPacket} to {@link WebResourceRequestPacket}</li>
|
||||||
|
* <li>Server-side: map {@link WebResourceResponsePacket} back to {@link WebResponsePacket}</li>
|
||||||
|
* <li>Client-side: map 1.0.0 responses/streams to v1.0.1 packets so v1.0.1 hooks can be reused</li>
|
||||||
|
* </ul>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public final class WebCompatMapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synthetic request id generator for 1.0.0 requests.
|
||||||
|
*/
|
||||||
|
private static final AtomicLong COMPAT_REQUEST_ID = new AtomicLong(1L);
|
||||||
|
|
||||||
|
private WebCompatMapper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a monotonic synthetic request id.
|
||||||
|
*
|
||||||
|
* @return next request id
|
||||||
|
*/
|
||||||
|
public static long nextCompatRequestId() {
|
||||||
|
return COMPAT_REQUEST_ID.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a v1.0.0 {@link WebRequestPacket} to a v1.0.1 {@link WebResourceRequestPacket}.
|
||||||
|
*
|
||||||
|
* <p>Because v1.0.0 has no tab/page/frame/request correlation model,
|
||||||
|
* synthetic identifiers must be provided.</p>
|
||||||
|
*
|
||||||
|
* @param requestId synthetic request id
|
||||||
|
* @param tabId synthetic tab id
|
||||||
|
* @param pageId synthetic page id
|
||||||
|
* @param requestPacket 1.0.0 request
|
||||||
|
* @return mapped v1.0.1 resource request
|
||||||
|
*/
|
||||||
|
public static WebResourceRequestPacket toResourceRequest(
|
||||||
|
long requestId,
|
||||||
|
long tabId,
|
||||||
|
long pageId,
|
||||||
|
WebRequestPacket requestPacket, ProtocolBridge bridge
|
||||||
|
) {
|
||||||
|
Objects.requireNonNull(requestPacket, "requestPacket");
|
||||||
|
|
||||||
|
String path = requestPacket.getPath();
|
||||||
|
String method = mapMethod(requestPacket.getMethod());
|
||||||
|
Map<String, String> headers = requestPacket.getHeaders() != null ? requestPacket.getHeaders() : Collections.emptyMap();
|
||||||
|
byte[] body = requestPacket.getBody() != null ? requestPacket.getBody() : new byte[0];
|
||||||
|
|
||||||
|
WebPacketHeader header = new WebPacketHeader(
|
||||||
|
requestId,
|
||||||
|
tabId,
|
||||||
|
pageId,
|
||||||
|
0L,
|
||||||
|
WebPacketFlags.RESOURCE,
|
||||||
|
System.currentTimeMillis()
|
||||||
|
);
|
||||||
|
|
||||||
|
return new WebResourceRequestPacket(
|
||||||
|
header,
|
||||||
|
path,
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
null,
|
||||||
|
WebInitiatorType.OTHER,
|
||||||
|
WebCacheMode.DEFAULT, bridge
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a v1.0.1 {@link WebResourceResponsePacket} back to a 1.0.0 {@link WebResponsePacket}.
|
||||||
|
*
|
||||||
|
* <p>Correlation information is lost because v1.0.0 does not support it.</p>
|
||||||
|
*
|
||||||
|
* @param response v1.0.1 resource response
|
||||||
|
* @return 1.0.0 response packet
|
||||||
|
*/
|
||||||
|
public static WebResponsePacket to100BResponse(WebResourceResponsePacket response) {
|
||||||
|
Objects.requireNonNull(response, "response");
|
||||||
|
|
||||||
|
return new WebResponsePacket(
|
||||||
|
response.getStatusCode(),
|
||||||
|
response.getContentType(),
|
||||||
|
response.getHeaders(),
|
||||||
|
response.getBody()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps v1.0.1 resource response to 1.0.0 response with overridden body.
|
||||||
|
*
|
||||||
|
* <p>Useful when buffering streamed responses for 1.0.0 clients.</p>
|
||||||
|
*
|
||||||
|
* @param response original v1.0.1 response
|
||||||
|
* @param body buffered full body
|
||||||
|
* @return 1.0.0 response
|
||||||
|
*/
|
||||||
|
public static WebResponsePacket to100BResponse(WebResourceResponsePacket response, byte[] body) {
|
||||||
|
Objects.requireNonNull(response, "response");
|
||||||
|
|
||||||
|
return new WebResponsePacket(
|
||||||
|
response.getStatusCode(),
|
||||||
|
response.getContentType(),
|
||||||
|
response.getHeaders(),
|
||||||
|
body != null ? body : new byte[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a v1.0.0 {@link WebResponsePacket} to a v1.0.1 {@link WebResourceResponsePacket}
|
||||||
|
* using the provided synthetic correlation header.
|
||||||
|
*
|
||||||
|
* @param correlationHeader synthetic correlation header (requestId/tabId/pageId/frameId)
|
||||||
|
* @param responsePacket 1.0.0 response packet
|
||||||
|
* @return v1.0.1 resource response packet
|
||||||
|
*/
|
||||||
|
public static WebResourceResponsePacket toV101ResourceResponse(WebPacketHeader correlationHeader, WebResponsePacket responsePacket) {
|
||||||
|
Objects.requireNonNull(correlationHeader, "correlationHeader");
|
||||||
|
Objects.requireNonNull(responsePacket, "responsePacket");
|
||||||
|
|
||||||
|
WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
|
||||||
|
|
||||||
|
return new WebResourceResponsePacket(
|
||||||
|
header,
|
||||||
|
responsePacket.getStatusCode(),
|
||||||
|
responsePacket.getContentType(),
|
||||||
|
responsePacket.getHeaders(),
|
||||||
|
responsePacket.getBody(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a v1.0.0 {@link WebStreamStartPacket_v1_0_0_B} to a v1.0.1 {@link WebStreamStartPacket_v1_0_1_B}
|
||||||
|
* using the provided synthetic correlation header.
|
||||||
|
*
|
||||||
|
* @param correlationHeader synthetic correlation header
|
||||||
|
* @param streamPacket 1.0.0 stream start
|
||||||
|
* @return v1.0.1 stream start
|
||||||
|
*/
|
||||||
|
public static WebStreamStartPacket_v1_0_1_B toV101StreamStart(WebPacketHeader correlationHeader, WebStreamStartPacket_v1_0_0_B streamPacket) {
|
||||||
|
Objects.requireNonNull(correlationHeader, "correlationHeader");
|
||||||
|
Objects.requireNonNull(streamPacket, "streamPacket");
|
||||||
|
|
||||||
|
WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
|
||||||
|
|
||||||
|
return new WebStreamStartPacket_v1_0_1_B(
|
||||||
|
header,
|
||||||
|
streamPacket.getStatusCode(),
|
||||||
|
streamPacket.getContentType(),
|
||||||
|
streamPacket.getHeaders(),
|
||||||
|
streamPacket.getTotalLength()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a v1.0.0 {@link WebStreamChunkPacket_v1_0_0_B} to a v1.0.1 {@link WebStreamChunkPacket_v1_0_1_B}
|
||||||
|
* using the provided synthetic correlation header.
|
||||||
|
*
|
||||||
|
* @param correlationHeader synthetic correlation header
|
||||||
|
* @param streamPacket 1.0.0 stream chunk
|
||||||
|
* @return v1.0.1 stream chunk
|
||||||
|
*/
|
||||||
|
public static WebStreamChunkPacket_v1_0_1_B toV101StreamChunk(WebPacketHeader correlationHeader, WebStreamChunkPacket_v1_0_0_B streamPacket) {
|
||||||
|
Objects.requireNonNull(correlationHeader, "correlationHeader");
|
||||||
|
Objects.requireNonNull(streamPacket, "streamPacket");
|
||||||
|
|
||||||
|
WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
|
||||||
|
|
||||||
|
return new WebStreamChunkPacket_v1_0_1_B(
|
||||||
|
header,
|
||||||
|
streamPacket.getSeq(),
|
||||||
|
streamPacket.getData()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a v1.0.0 {@link WebStreamEndPacket_v1_0_0_B} to a v1.0.1 {@link WebStreamEndPacket_v1_0_1_B}
|
||||||
|
* using the provided synthetic correlation header.
|
||||||
|
*
|
||||||
|
* @param correlationHeader synthetic correlation header
|
||||||
|
* @param streamPacket v1.0.0 stream end packet
|
||||||
|
* @return v1.0.1 stream end packet
|
||||||
|
*/
|
||||||
|
public static WebStreamEndPacket_v1_0_1_B toV101StreamEnd(
|
||||||
|
WebPacketHeader correlationHeader,
|
||||||
|
WebStreamEndPacket_v1_0_0_B streamPacket
|
||||||
|
) {
|
||||||
|
Objects.requireNonNull(correlationHeader, "correlationHeader");
|
||||||
|
Objects.requireNonNull(streamPacket, "streamPacket");
|
||||||
|
|
||||||
|
WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
|
||||||
|
|
||||||
|
// v1.0.0 has no error string -> null
|
||||||
|
return new WebStreamEndPacket_v1_0_1_B(
|
||||||
|
header,
|
||||||
|
streamPacket.isOk(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a header that mirrors correlation fields from the provided header and updates timestamp/flags.
|
||||||
|
*
|
||||||
|
* @param correlationHeader correlation header to mirror
|
||||||
|
* @param extraFlags flags to OR
|
||||||
|
* @return mirrored header
|
||||||
|
*/
|
||||||
|
public static WebPacketHeader mirrorCorrelation(WebPacketHeader correlationHeader, int extraFlags) {
|
||||||
|
Objects.requireNonNull(correlationHeader, "correlationHeader");
|
||||||
|
return new WebPacketHeader(
|
||||||
|
correlationHeader.getRequestId(),
|
||||||
|
correlationHeader.getTabId(),
|
||||||
|
correlationHeader.getPageId(),
|
||||||
|
correlationHeader.getFrameId(),
|
||||||
|
correlationHeader.getFlags() | extraFlags,
|
||||||
|
System.currentTimeMillis()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps 1.0.0 {@link WebRequestMethod}.
|
||||||
|
*
|
||||||
|
* @param method 1.0.0 method
|
||||||
|
* @return method string
|
||||||
|
*/
|
||||||
|
public static String mapMethod(WebRequestMethod method) {
|
||||||
|
if (method == null) return "GET";
|
||||||
|
return switch (method) {
|
||||||
|
case GET -> "GET";
|
||||||
|
case POST, EXECUTE, SCRIPT -> "POST";
|
||||||
|
default -> "GET";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps 1.0.1 to 1.0.0 {@link WebRequestMethod}.
|
||||||
|
*
|
||||||
|
* @param method 1.0.0 method
|
||||||
|
* @return method string
|
||||||
|
*/
|
||||||
|
public static WebRequestMethod map100BMethod(String method) {
|
||||||
|
if (method == null) return WebRequestMethod.GET;
|
||||||
|
return switch (method.toUpperCase()) {
|
||||||
|
case "POST", "SCRIPT", "EXECUTE" -> WebRequestMethod.POST;
|
||||||
|
default -> WebRequestMethod.GET;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for creating {@link WebPacketHeader} instances with stable tab/page identifiers and
|
||||||
|
* monotonic request identifiers.
|
||||||
|
*
|
||||||
|
* <p>This class is intended to be used by the WebClient/WebServer integration layer to ensure
|
||||||
|
* every packet carries a valid header and consistent correlation metadata.</p>
|
||||||
|
*
|
||||||
|
* <p>Thread-safety:
|
||||||
|
* <ul>
|
||||||
|
* <li>Request id generation is thread-safe.</li>
|
||||||
|
* <li>Tab/page allocation methods are thread-safe.</li>
|
||||||
|
* <li>{@link WebTabContext} is thread-safe for reads; writes are synchronized.</li>
|
||||||
|
* </ul>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public final class WebHeaderFactory {
|
||||||
|
|
||||||
|
private static final long MIN_ID = 1L;
|
||||||
|
|
||||||
|
private final AtomicLong requestIdSeq;
|
||||||
|
private final AtomicLong tabIdSeq;
|
||||||
|
private final AtomicLong pageIdSeq;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a factory with randomized id bases to reduce collisions across process restarts.
|
||||||
|
*/
|
||||||
|
public WebHeaderFactory() {
|
||||||
|
SecureRandom rnd = new SecureRandom();
|
||||||
|
this.requestIdSeq = new AtomicLong(Math.max(MIN_ID, rnd.nextLong() & Long.MAX_VALUE));
|
||||||
|
this.tabIdSeq = new AtomicLong(Math.max(MIN_ID, rnd.nextLong() & Long.MAX_VALUE));
|
||||||
|
this.pageIdSeq = new AtomicLong(Math.max(MIN_ID, rnd.nextLong() & Long.MAX_VALUE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new tab context with a fresh tab id and an initial page id.
|
||||||
|
*
|
||||||
|
* @return new tab context
|
||||||
|
*/
|
||||||
|
public WebTabContext createTab() {
|
||||||
|
long tabId = nextTabId();
|
||||||
|
long pageId = nextPageId();
|
||||||
|
return new WebTabContext(tabId, pageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a new navigation by allocating a new page id for the given tab context.
|
||||||
|
*
|
||||||
|
* <p>Call this when navigation is initiated (typed URL, link click, reload, back/forward).</p>
|
||||||
|
*
|
||||||
|
* @param tab tab context
|
||||||
|
* @return the new page id
|
||||||
|
*/
|
||||||
|
public long beginNavigation(WebTabContext tab) {
|
||||||
|
Objects.requireNonNull(tab, "tab");
|
||||||
|
long newPageId = nextPageId();
|
||||||
|
tab.setPageId(newPageId);
|
||||||
|
return newPageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a header for a navigation request.
|
||||||
|
*
|
||||||
|
* @param tab tab context
|
||||||
|
* @param noStore if true, sets {@link WebPacketFlags#NO_STORE}
|
||||||
|
* @return header
|
||||||
|
*/
|
||||||
|
public WebPacketHeader navigation(WebTabContext tab, boolean noStore) {
|
||||||
|
Objects.requireNonNull(tab, "tab");
|
||||||
|
int flags = WebPacketFlags.NAVIGATION;
|
||||||
|
if (noStore) flags |= WebPacketFlags.NO_STORE;
|
||||||
|
return header(nextRequestId(), tab.getTabId(), tab.getPageId(), 0L, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a header for a resource request.
|
||||||
|
*
|
||||||
|
* @param tab tab context
|
||||||
|
* @param frameId frame id (0 = main)
|
||||||
|
* @param noStore if true, sets {@link WebPacketFlags#NO_STORE}
|
||||||
|
* @return header
|
||||||
|
*/
|
||||||
|
public WebPacketHeader resource(WebTabContext tab, long frameId, boolean noStore) {
|
||||||
|
Objects.requireNonNull(tab, "tab");
|
||||||
|
int flags = WebPacketFlags.RESOURCE;
|
||||||
|
if (noStore) flags |= WebPacketFlags.NO_STORE;
|
||||||
|
return header(nextRequestId(), tab.getTabId(), tab.getPageId(), frameId, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a header for a resource response correlated to a request id.
|
||||||
|
*
|
||||||
|
* @param requestId request id to correlate
|
||||||
|
* @param tabId tab id
|
||||||
|
* @param pageId page id
|
||||||
|
* @param frameId frame id
|
||||||
|
* @return header
|
||||||
|
*/
|
||||||
|
public WebPacketHeader resourceResponse(long requestId, long tabId, long pageId, long frameId) {
|
||||||
|
return header(requestId, tabId, pageId, frameId, WebPacketFlags.RESOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a header for a streamed payload associated with a request id.
|
||||||
|
*
|
||||||
|
* @param requestId correlated request id
|
||||||
|
* @param tabId tab id
|
||||||
|
* @param pageId page id
|
||||||
|
* @param frameId frame id
|
||||||
|
* @return header
|
||||||
|
*/
|
||||||
|
public WebPacketHeader stream(long requestId, long tabId, long pageId, long frameId) {
|
||||||
|
return header(requestId, tabId, pageId, frameId, WebPacketFlags.STREAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an event header (server->client or client->server).
|
||||||
|
*
|
||||||
|
* @param tabId tab id
|
||||||
|
* @param pageId page id
|
||||||
|
* @param frameId frame id
|
||||||
|
* @return header
|
||||||
|
*/
|
||||||
|
public WebPacketHeader event(long tabId, long pageId, long frameId) {
|
||||||
|
return header(nextRequestId(), tabId, pageId, frameId, WebPacketFlags.EVENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an event header correlated to an existing request id.
|
||||||
|
*
|
||||||
|
* @param requestId request id
|
||||||
|
* @param tabId tab id
|
||||||
|
* @param pageId page id
|
||||||
|
* @param frameId frame id
|
||||||
|
* @return header
|
||||||
|
*/
|
||||||
|
public WebPacketHeader correlatedEvent(long requestId, long tabId, long pageId, long frameId) {
|
||||||
|
return header(requestId, tabId, pageId, frameId, WebPacketFlags.EVENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a header for a document snapshot event.
|
||||||
|
*
|
||||||
|
* @param tab tab context
|
||||||
|
* @param frameId frame id
|
||||||
|
* @return header
|
||||||
|
*/
|
||||||
|
public WebPacketHeader documentSnapshotEvent(WebTabContext tab, long frameId) {
|
||||||
|
Objects.requireNonNull(tab, "tab");
|
||||||
|
return header(nextRequestId(), tab.getTabId(), tab.getPageId(), frameId, WebPacketFlags.EVENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a header for a document apply request.
|
||||||
|
*
|
||||||
|
* @param tab tab context
|
||||||
|
* @param frameId frame id
|
||||||
|
* @return header
|
||||||
|
*/
|
||||||
|
public WebPacketHeader documentApply(WebTabContext tab, long frameId) {
|
||||||
|
Objects.requireNonNull(tab, "tab");
|
||||||
|
return header(nextRequestId(), tab.getTabId(), tab.getPageId(), frameId, WebPacketFlags.RESOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether an incoming packet is stale for a given tab context.
|
||||||
|
*
|
||||||
|
* <p>Stale means: the packet refers to an older pageId than the current tab pageId.</p>
|
||||||
|
*
|
||||||
|
* @param tab tab context
|
||||||
|
* @param incoming incoming header
|
||||||
|
* @return true if stale, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isStale(WebTabContext tab, WebPacketHeader incoming) {
|
||||||
|
Objects.requireNonNull(tab, "tab");
|
||||||
|
Objects.requireNonNull(incoming, "incoming");
|
||||||
|
return incoming.getTabId() == tab.getTabId() && incoming.getPageId() != tab.getPageId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebPacketHeader header(long requestId, long tabId, long pageId, long frameId, int flags) {
|
||||||
|
long ts = System.currentTimeMillis();
|
||||||
|
return new WebPacketHeader(requestId, tabId, pageId, frameId, flags, ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long nextRequestId() {
|
||||||
|
long v = requestIdSeq.incrementAndGet();
|
||||||
|
return (v < MIN_ID) ? MIN_ID : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long nextTabId() {
|
||||||
|
long v = tabIdSeq.incrementAndGet();
|
||||||
|
return (v < MIN_ID) ? MIN_ID : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long nextPageId() {
|
||||||
|
long v = pageIdSeq.incrementAndGet();
|
||||||
|
return (v < MIN_ID) ? MIN_ID : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents per-tab state used for header creation and stale detection.
|
||||||
|
*/
|
||||||
|
public static final class WebTabContext {
|
||||||
|
|
||||||
|
private final long tabId;
|
||||||
|
private volatile long pageId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a tab context.
|
||||||
|
*
|
||||||
|
* @param tabId tab id
|
||||||
|
* @param pageId initial page id
|
||||||
|
*/
|
||||||
|
public WebTabContext(long tabId, long pageId) {
|
||||||
|
if (tabId < MIN_ID) throw new IllegalArgumentException("tabId must be >= 1");
|
||||||
|
if (pageId < MIN_ID) throw new IllegalArgumentException("pageId must be >= 1");
|
||||||
|
this.tabId = tabId;
|
||||||
|
this.pageId = pageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return stable tab id
|
||||||
|
*/
|
||||||
|
public long getTabId() {
|
||||||
|
return tabId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return current page id
|
||||||
|
*/
|
||||||
|
public long getPageId() {
|
||||||
|
return pageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current page id.
|
||||||
|
*
|
||||||
|
* @param pageId new page id
|
||||||
|
*/
|
||||||
|
public synchronized void setPageId(long pageId) {
|
||||||
|
if (pageId < MIN_ID) throw new IllegalArgumentException("pageId must be >= 1");
|
||||||
|
this.pageId = pageId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies the initiator of a resource request.
|
||||||
|
*/
|
||||||
|
public enum WebInitiatorType {
|
||||||
|
DOCUMENT,
|
||||||
|
SCRIPT,
|
||||||
|
STYLE,
|
||||||
|
IMAGE,
|
||||||
|
MEDIA,
|
||||||
|
FONT,
|
||||||
|
XHR,
|
||||||
|
FETCH,
|
||||||
|
OTHER
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bitset flags used in WebPacketHeader.
|
||||||
|
*/
|
||||||
|
public final class WebPacketFlags {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Packet describes a top-level navigation action.
|
||||||
|
*/
|
||||||
|
public static final int NAVIGATION = 1 << 0;
|
||||||
|
/**
|
||||||
|
* Packet describes a resource request/response.
|
||||||
|
*/
|
||||||
|
public static final int RESOURCE = 1 << 1;
|
||||||
|
/**
|
||||||
|
* Packet is part of a streamed body transfer.
|
||||||
|
*/
|
||||||
|
public static final int STREAM = 1 << 2;
|
||||||
|
/**
|
||||||
|
* Packet is devtools-related.
|
||||||
|
*/
|
||||||
|
public static final int DEVTOOLS = 1 << 3;
|
||||||
|
/**
|
||||||
|
* Packet is an event (server->client / client->server).
|
||||||
|
*/
|
||||||
|
public static final int EVENT = 1 << 4;
|
||||||
|
/**
|
||||||
|
* Disable caching / do not store.
|
||||||
|
*/
|
||||||
|
public static final int NO_STORE = 1 << 5;
|
||||||
|
|
||||||
|
private WebPacketFlags() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a flag is present.
|
||||||
|
*
|
||||||
|
* @param flags bitset
|
||||||
|
* @param flag flag constant
|
||||||
|
* @return true if present
|
||||||
|
*/
|
||||||
|
public static boolean has(int flags, int flag) {
|
||||||
|
return (flags & flag) == flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user