Compare commits
55 Commits
7321d06ee9
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4a1da00ab | ||
|
|
83e3d425c9 | ||
|
|
3dcb926be2 | ||
|
|
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 |
51
.idea/misc.xml
generated
51
.idea/misc.xml
generated
@@ -13,7 +13,56 @@
|
|||||||
</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="NullableNotNullManager">
|
||||||
|
<option name="myDefaultNullable" value="org.jetbrains.annotations.Nullable" />
|
||||||
|
<option name="myDefaultNotNull" value="org.jetbrains.annotations.NotNull" />
|
||||||
|
<option name="myOrdered" value="false" />
|
||||||
|
<option name="myNullables">
|
||||||
|
<value>
|
||||||
|
<list size="16">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="org.jspecify.annotations.Nullable" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="android.annotation.Nullable" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
|
||||||
|
<item index="5" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
||||||
|
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
|
||||||
|
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
|
||||||
|
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
|
||||||
|
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
|
||||||
|
<item index="10" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
|
||||||
|
<item index="11" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
||||||
|
<item index="12" class="java.lang.String" itemvalue="jakarta.annotation.Nullable" />
|
||||||
|
<item index="13" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||||
|
<item index="14" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||||
|
<item index="15" class="java.lang.String" itemvalue="org.springframework.lang.Nullable" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="myNotNulls">
|
||||||
|
<value>
|
||||||
|
<list size="16">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="org.jspecify.annotations.NonNull" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="android.annotation.NonNull" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
|
||||||
|
<item index="5" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
||||||
|
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
|
||||||
|
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
|
||||||
|
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
|
||||||
|
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
|
||||||
|
<item index="10" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
|
||||||
|
<item index="11" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
||||||
|
<item index="12" class="java.lang.String" itemvalue="jakarta.annotation.Nonnull" />
|
||||||
|
<item index="13" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||||
|
<item index="14" class="java.lang.String" itemvalue="lombok.NonNull" />
|
||||||
|
<item index="15" class="java.lang.String" itemvalue="org.springframework.lang.NonNull" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<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>
|
||||||
72
LICENSE
72
LICENSE
@@ -1,68 +1,14 @@
|
|||||||
Open Autonomous Public License (OAPL) v1.0
|
Open Autonomous Public License (OAPL)
|
||||||
Copyright (C) 2024-2025 Open Autonomous Connection (OAC)
|
|
||||||
Projekt-URL: https://open-autonomous-connection.org/
|
|
||||||
|
|
||||||
---
|
Copyright (C) Open Autonomous Connection (OAC)
|
||||||
|
|
||||||
1. Nutzungsrechte
|
This project is licensed under the Open Autonomous Public License (OAPL),
|
||||||
Diese Software darf sowohl für private als auch kommerzielle Zwecke genutzt werden. Die Nutzung ist unter den Bedingungen dieser Lizenz gestattet.
|
version 1.1 or any later version published by the Open Autonomous Connection.
|
||||||
|
|
||||||
---
|
The full and authoritative license text is available at:
|
||||||
|
https://open-autonomous-connection.org/license.html
|
||||||
|
|
||||||
2. Verkaufsverbot
|
By using, modifying, or distributing this software, you agree to the terms
|
||||||
Es ist nicht gestattet, diese Software oder abgeleitete Werke davon zu verkaufen oder kommerziell zu vertreiben.
|
of the latest applicable OAPL version.
|
||||||
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.
|
|
||||||
|
|
||||||
---
|
NOTICE: Third party licenses can be found in resources/licenses
|
||||||
|
|
||||||
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.
|
|
||||||
147
LICENSE-oapl1.1
Normal file
147
LICENSE-oapl1.1
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
Open Autonomous Public License (OAPL) v1.1
|
||||||
|
Sovereign Network Copyleft License
|
||||||
|
|
||||||
|
Copyright (C) Open Autonomous Connection (OAC)
|
||||||
|
Project URL: https://open-autonomous-connection.org/
|
||||||
|
|
||||||
|
0. Definitions
|
||||||
|
|
||||||
|
“OAC Software”
|
||||||
|
Means any software distributed under this License, including but not limited to the Open Autonomous Connection protocol, reference implementations, clients, servers, INS systems, APIs, libraries, and the WebPage framework.
|
||||||
|
|
||||||
|
“Covered Work”
|
||||||
|
Means the OAC Software and any Derivative Work thereof.
|
||||||
|
|
||||||
|
“Derivative Work”
|
||||||
|
A work shall be considered a Derivative Work if it:
|
||||||
|
1. Modifies the OAC Software;
|
||||||
|
2. Incorporates it;
|
||||||
|
3. Links to it, whether statically or dynamically;
|
||||||
|
4. Extends its public APIs in a manner requiring OAC at runtime;
|
||||||
|
5. Implements required OAC interfaces;
|
||||||
|
6. Is designed to operate exclusively within the OAC runtime environment.
|
||||||
|
|
||||||
|
For clarity: A class extending “WebPage” is considered a Derivative Work.
|
||||||
|
|
||||||
|
“Independent Content”
|
||||||
|
Data or media files that do not contain executable OAC-dependent logic and can exist independently of OAC. Examples include HTML files, CSS files, images, JSON data, and static documents.
|
||||||
|
|
||||||
|
“Network Deployment”
|
||||||
|
Making a Covered Work available to third parties over any network, including but not limited to SaaS, cloud hosting, managed services, container environments, remote execution systems, and reverse-proxied deployments.
|
||||||
|
|
||||||
|
“Software Monetisation”
|
||||||
|
Any direct or indirect financial compensation in exchange for licensing, distributing, sublicensing, selling, restricting access to, or otherwise commercially exploiting the Covered Work itself as software.
|
||||||
|
|
||||||
|
“Content Monetisation”
|
||||||
|
Commercial activity based on content transmitted through OAC, provided that no Software Monetisation occurs.
|
||||||
|
|
||||||
|
1. Grant of License
|
||||||
|
|
||||||
|
Subject to compliance with this License, you are granted a worldwide, royalty-free, non-exclusive, non-transferable right to use, copy, study, modify, and distribute the Covered Work.
|
||||||
|
|
||||||
|
All rights not expressly granted remain reserved.
|
||||||
|
|
||||||
|
2. Strong Copyleft
|
||||||
|
|
||||||
|
Any Derivative Work:
|
||||||
|
1. Must be licensed exclusively under this OAPL v1.1;
|
||||||
|
2. Must include the full, unmodified text of this License;
|
||||||
|
3. Must clearly document all modifications;
|
||||||
|
4. Must provide full corresponding source code;
|
||||||
|
5. Must not impose additional restrictions beyond this License.
|
||||||
|
|
||||||
|
Re-licensing under any other license is prohibited.
|
||||||
|
|
||||||
|
3. Network Copyleft
|
||||||
|
|
||||||
|
If a Covered Work is subject to Network Deployment:
|
||||||
|
1. The complete corresponding source code must be publicly accessible;
|
||||||
|
2. Access must not require payment;
|
||||||
|
3. Access must not require authentication beyond reasonable technical protections such as rate-limiting;
|
||||||
|
4. Build scripts and sufficient instructions for reproducible builds must be provided.
|
||||||
|
|
||||||
|
This obligation applies regardless of whether distribution occurs.
|
||||||
|
|
||||||
|
4. Prohibition of Software Monetisation
|
||||||
|
|
||||||
|
The Covered Work may not be:
|
||||||
|
- Sold;
|
||||||
|
- Licensed for a fee;
|
||||||
|
- Distributed against financial compensation;
|
||||||
|
- Placed behind a paywall;
|
||||||
|
- Restricted by license keys or technical access controls intended to require payment;
|
||||||
|
- Otherwise monetised as software.
|
||||||
|
|
||||||
|
Charging for infrastructure, hosting, support, consulting, or independent services is permitted, provided no Software Monetisation occurs.
|
||||||
|
|
||||||
|
5. Content Monetisation
|
||||||
|
|
||||||
|
Independent Content transmitted through OAC may be monetised without restriction, provided the Covered Work itself is not monetised.
|
||||||
|
|
||||||
|
6. Patent License
|
||||||
|
|
||||||
|
Each contributor grants a perpetual, worldwide, royalty-free patent license covering any patent claims necessarily infringed by their contributions to the Covered Work.
|
||||||
|
|
||||||
|
If any party initiates patent litigation alleging that the Covered Work infringes their patent rights, that party’s rights under this License terminate immediately.
|
||||||
|
|
||||||
|
7. Contributor Terms
|
||||||
|
|
||||||
|
By submitting contributions to the Covered Work, you grant the original copyright holder the right to:
|
||||||
|
- Enforce this License;
|
||||||
|
- Publish future versions of the OAPL;
|
||||||
|
- Re-license your contribution under later versions of the OAPL.
|
||||||
|
|
||||||
|
Contributors retain their copyright.
|
||||||
|
|
||||||
|
8. No Proprietary Integration
|
||||||
|
|
||||||
|
The Covered Work may not be combined with software that:
|
||||||
|
- Prevents full source disclosure;
|
||||||
|
- Imposes additional restrictions;
|
||||||
|
- Conflicts with this License.
|
||||||
|
|
||||||
|
If full compliance is not possible, distribution is prohibited.
|
||||||
|
|
||||||
|
9. Trademark
|
||||||
|
|
||||||
|
This License does not grant any rights to use the trademarks, trade names, service marks, or product names of the original copyright holder.
|
||||||
|
|
||||||
|
The terms:
|
||||||
|
- “Open Autonomous Connection”
|
||||||
|
- “OAC”
|
||||||
|
- “OAC-Compatible”
|
||||||
|
|
||||||
|
may only be used:
|
||||||
|
1. For unmodified versions of the OAC Software; or
|
||||||
|
2. For fully compliant implementations under the OAC Compliance Specification; or
|
||||||
|
3. With explicit written permission from the trademark holder.
|
||||||
|
|
||||||
|
Modified or forked versions must remove all references to OAC trademarks unless permission is granted.
|
||||||
|
|
||||||
|
10. Cure Period
|
||||||
|
|
||||||
|
Any violation of this License results in automatic termination of rights.
|
||||||
|
|
||||||
|
If the violation is cured within 30 days of written notice, the License is automatically reinstated.
|
||||||
|
|
||||||
|
11. Warranty Disclaimer
|
||||||
|
|
||||||
|
THE COVERED WORK IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
|
||||||
|
|
||||||
|
12. Limitation of Liability
|
||||||
|
|
||||||
|
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE AUTHORS SHALL NOT BE LIABLE FOR ANY DAMAGES ARISING FROM THE USE OF THE COVERED WORK.
|
||||||
|
|
||||||
|
13. Severability
|
||||||
|
|
||||||
|
If any provision of this License is held to be unenforceable, the remaining provisions shall remain in full force and effect.
|
||||||
|
|
||||||
|
Such provision shall be interpreted to most closely reflect its original intent within the limits of applicable law.
|
||||||
|
|
||||||
|
14. Governing Law
|
||||||
|
|
||||||
|
This License shall be governed by the laws of the Federal Republic of Germany.
|
||||||
|
|
||||||
|
Exclusive place of jurisdiction shall be the registered seat of the copyright holder, unless mandatory law provides otherwise.
|
||||||
|
|
||||||
|
End of License.
|
||||||
107
README.MD
107
README.MD
@@ -1,35 +1,35 @@
|
|||||||
# Open Autonomous Connection Protocol
|
# Open Autonomous Connection Protocol
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> This is the classic version!
|
|
||||||
> Classic version is not longer maintained or supported please switch the Branch to master
|
|
||||||
> The new Protocol has a Backwards compatibility and is supporting the classic Version
|
|
||||||
|
|
||||||
This is the Protocol for our Open Autonomous Connection project.<br />
|
This is the Protocol for our Open Autonomous Connection project.<br />
|
||||||
You can easily implement this Protocol via Maven.<br />
|
You can easily implement this Protocol via Maven.<br />
|
||||||
Feel free to join our Discord.
|
Feel free to join our Discord.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
|
||||||
|
|
||||||
# Bugs/Problems
|
**Third-party components:**
|
||||||
# In progress
|
<br />
|
||||||
- Cleanup Code
|
Download all license here: https://open-autonomous-connection.org/assets/licenses.zip
|
||||||
# TODO
|
- *UnlegitLibrary* is authored by the same copyright holder and is used here under a special agreement:
|
||||||
- Subdomains
|
While [UnlegitLibrary](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/) is generally distributed under
|
||||||
- Fragments
|
the [GNU GPLv3](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/src/branch/master/LICENSE),
|
||||||
|
it is additionally licensed under OAPL **exclusively for the OAC project**.
|
||||||
|
Therefore, within OAC, the OAPL terms apply to UnlegitLibrary as well.
|
||||||
|
|
||||||
|
### Take a look into [Certificate Generation](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/src/branch/master#certificate-generation-for-networksystem).
|
||||||
|
|
||||||
# Maven
|
# Maven
|
||||||
|
|
||||||
### pom.xml
|
### pom.xml
|
||||||
|
|
||||||
```
|
```
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>me.openautonomousconnection</groupId>
|
<groupId>org.openautonomousconnection</groupId>
|
||||||
<artifactId>protocol</artifactId>
|
<artifactId>Protocol</artifactId>
|
||||||
<version>1.0.0-Classic</version>
|
<version>VERSION</version>
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Repository:
|
### Repository:
|
||||||
|
|
||||||
```
|
```
|
||||||
<repository>
|
<repository>
|
||||||
<id>oac</id>
|
<id>oac</id>
|
||||||
@@ -39,78 +39,3 @@ Feel free to join our Discord.
|
|||||||
</snapshots>
|
</snapshots>
|
||||||
</repository>
|
</repository>
|
||||||
```
|
```
|
||||||
|
|
||||||
# Examples
|
|
||||||
#### Note: These examples are very basic
|
|
||||||
### Server
|
|
||||||
|
|
||||||
```java
|
|
||||||
import me.finn.unlegitlibrary.network.system.server.ConnectionHandler;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolSettings;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolVersion;
|
|
||||||
import side.org.openautonomousconnection.protocol.ProtocolServer;
|
|
||||||
|
|
||||||
public class Server extends ProtocolServer {
|
|
||||||
|
|
||||||
public Server() throws IOException, InterruptedException {
|
|
||||||
super(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
try {
|
|
||||||
ProtocolBridge protocolBridge = new ProtocolBridge(ProtocolVersion.PV_1_0_0, new ProtocolSettings(), new Server());
|
|
||||||
protocolBridge.getProtocolServer().setProtocolBridge(protocolBridge);
|
|
||||||
protocolBridge.getProtocolServer().startServer();
|
|
||||||
} catch (IOException | InterruptedException | InvocationTargetException | InstantiationException |
|
|
||||||
IllegalAccessException | NoSuchMethodException exception) {
|
|
||||||
exception.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Domain> getDomains() throws SQLException {
|
|
||||||
return List.of(); // Your method here to get all registered domains
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleMessage(ConnectionHandler connectionHandler, String message) {
|
|
||||||
System.out.println("Received message: " + message + " from client: " + connectionHandler.getClientID());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
### Client
|
|
||||||
|
|
||||||
```java
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolSettings;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolVersion;
|
|
||||||
import domain.org.openautonomousconnection.protocol.Domain;
|
|
||||||
import side.org.openautonomousconnection.protocol.ProtocolClient;
|
|
||||||
import utils.org.openautonomousconnection.protocol.SiteType;
|
|
||||||
|
|
||||||
public class Client extends ProtocolClient {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
try {
|
|
||||||
ProtocolBridge protocolBridge = new ProtocolBridge(ProtocolVersion.PV_1_0_0, new ProtocolSettings(), new Client());
|
|
||||||
protocolBridge.getProtocolClient().setProtocolBridge(protocolBridge);
|
|
||||||
protocolBridge.getProtocolServer().startClient();
|
|
||||||
} catch (IOException | InterruptedException | InvocationTargetException | InstantiationException |
|
|
||||||
IllegalAccessException | NoSuchMethodException exception) {
|
|
||||||
exception.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleHTMLContent(SiteType siteType, Domain domain, String htmlContent) {
|
|
||||||
System.out.println("Website html content received. This site is " + siteType.name);
|
|
||||||
System.out.println(htmlContent); // Render content in a webview for example
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleMessage(String message) {
|
|
||||||
System.out.println("Received message: " + message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
74
pom.xml
74
pom.xml
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>org.openautonomousconnection</groupId>
|
<groupId>org.openautonomousconnection</groupId>
|
||||||
<artifactId>Protocol</artifactId>
|
<artifactId>Protocol</artifactId>
|
||||||
<version>1.0.0-CLASSIC.1.0</version>
|
<version>1.0.1-BETA.0.8</version>
|
||||||
<organization>
|
<organization>
|
||||||
<name>Open Autonomous Connection</name>
|
<name>Open Autonomous Connection</name>
|
||||||
<url>https://open-autonomous-connection.org/</url>
|
<url>https://open-autonomous-connection.org/</url>
|
||||||
@@ -15,8 +15,8 @@
|
|||||||
<description>The Protocol for Server and Client</description>
|
<description>The Protocol for Server and Client</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>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>
|
||||||
|
|
||||||
@@ -56,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>
|
||||||
@@ -68,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>
|
||||||
|
|
||||||
@@ -78,12 +84,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>dev.unlegitdqrk</groupId>
|
<groupId>dev.unlegitdqrk</groupId>
|
||||||
<artifactId>unlegitlibrary</artifactId>
|
<artifactId>unlegitlibrary</artifactId>
|
||||||
<version>1.6.7</version>
|
<version>1.8.5</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>
|
||||||
@@ -91,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>
|
||||||
@@ -150,6 +149,9 @@
|
|||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
<version>3.6.3</version>
|
<version>3.6.3</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
|
<failOnError>false</failOnError>
|
||||||
|
<failOnWarnings>false</failOnWarnings>
|
||||||
|
<doclint>none</doclint>
|
||||||
<locale>en_US</locale>
|
<locale>en_US</locale>
|
||||||
<encoding>UTF-8</encoding>
|
<encoding>UTF-8</encoding>
|
||||||
<docencoding>UTF-8</docencoding>
|
<docencoding>UTF-8</docencoding>
|
||||||
|
|||||||
@@ -1,136 +1,357 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol;
|
package org.openautonomousconnection.protocol;
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
|
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.DomainPacket;
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode;
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.MessagePacket;
|
import lombok.Getter;
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.PingPacket;
|
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||||
import org.openautonomousconnection.protocol.side.ProtocolClient;
|
import org.openautonomousconnection.protocol.listeners.ClientListener;
|
||||||
import org.openautonomousconnection.protocol.side.ProtocolServer;
|
import org.openautonomousconnection.protocol.listeners.CustomServerListener;
|
||||||
import org.openautonomousconnection.protocol.utils.APIInformation;
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.AuthPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamChunkPacket_v1_0_0_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamEndPacket_v1_0_0_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamStartPacket_v1_0_0_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentSnapshotEventPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateAckPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamChunkPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamEndPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamStartPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
|
||||||
|
import org.openautonomousconnection.protocol.side.ins.ProtocolINSServer;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.ProtocolCustomServer;
|
||||||
|
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta.OacWebUrlInstaller_v1_0_0_B;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.LibClientImpl_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.OacUrlHandlerInstaller_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.ProtocolWebServer_1_0_0_B;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.util.function.Supplier;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
|
|
||||||
public class ProtocolBridge extends DefaultMethodsOverrider {
|
/**
|
||||||
private APIInformation apiInformation;
|
* The main bridge class for the protocol connection.
|
||||||
private final ProtocolSettings protocolSettings;
|
* It manages the protocol settings, version, and side instances.
|
||||||
private final ProtocolVersion protocolVersion;
|
*/
|
||||||
|
public final class ProtocolBridge {
|
||||||
|
/**
|
||||||
|
* The protocol settings for the current connection
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final ProtocolValues protocolValues;
|
||||||
|
|
||||||
private final ProtocolServer protocolServer;
|
/**
|
||||||
private final ProtocolClient protocolClient;
|
* The protocol side instances
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private ProtocolClient protocolClient;
|
||||||
|
|
||||||
public final boolean isRunningAsServer() {
|
|
||||||
return protocolServer != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final ProtocolClient getProtocolClient() {
|
/**
|
||||||
return protocolClient;
|
* The protocol side instances
|
||||||
}
|
*/
|
||||||
|
@Getter
|
||||||
|
private ProtocolCustomServer protocolServer;
|
||||||
|
|
||||||
public final ProtocolSettings getProtocolSettings() {
|
/**
|
||||||
return protocolSettings;
|
* Initialize the ProtocolBridge instance for the client side
|
||||||
}
|
*
|
||||||
|
* @param protocolServer The ProtocolCustomServer instance
|
||||||
public final ProtocolServer getProtocolServer() {
|
* @param protocolValues The ProtocolSettings instance
|
||||||
return protocolServer;
|
* @throws Exception if an error occurs while initializing the ProtocolBridge
|
||||||
}
|
*/
|
||||||
|
public ProtocolBridge(ProtocolCustomServer protocolServer, ProtocolValues protocolValues) throws Exception {
|
||||||
public final ProtocolVersion getProtocolVersion() {
|
// Assign the parameters to the class fields
|
||||||
return protocolVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ProtocolBridge instance;
|
|
||||||
|
|
||||||
public static ProtocolBridge getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProtocolBridge(ProtocolVersion protocolVersion, ProtocolSettings protocolSettings, ProtocolClient protocolClient, APIInformation apiInformation) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
|
|
||||||
this(protocolVersion, protocolSettings, protocolClient);
|
|
||||||
this.apiInformation = apiInformation;
|
|
||||||
instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProtocolBridge(ProtocolVersion protocolVersion, ProtocolSettings protocolSettings, ProtocolClient protocolClient) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
|
|
||||||
checkUpdates();
|
|
||||||
|
|
||||||
protocolSettings.packetHandler.registerPacket(new DomainPacket());
|
|
||||||
protocolSettings.packetHandler.registerPacket(new PingPacket());
|
|
||||||
protocolSettings.packetHandler.registerPacket(new MessagePacket());
|
|
||||||
|
|
||||||
this.protocolServer = null;
|
|
||||||
|
|
||||||
this.protocolVersion = protocolVersion;
|
|
||||||
this.protocolSettings = protocolSettings;
|
|
||||||
this.apiInformation = null;
|
|
||||||
this.protocolClient = protocolClient;
|
|
||||||
|
|
||||||
instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProtocolBridge(ProtocolVersion protocolVersion, ProtocolSettings protocolSettings, ProtocolServer protocolServer) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
|
|
||||||
checkUpdates();
|
|
||||||
|
|
||||||
this.apiInformation = null;
|
|
||||||
this.protocolClient = null;
|
|
||||||
|
|
||||||
this.protocolVersion = protocolVersion;
|
|
||||||
this.protocolSettings = protocolSettings;
|
|
||||||
this.protocolServer = protocolServer;
|
this.protocolServer = protocolServer;
|
||||||
|
this.protocolValues = protocolValues;
|
||||||
|
|
||||||
protocolSettings.packetHandler.registerPacket(new DomainPacket());
|
if (protocolServer instanceof ProtocolINSServer)
|
||||||
protocolSettings.packetHandler.registerPacket(new PingPacket());
|
protocolServer.attachBridge(this, null, false, ClientAuthMode.NONE);
|
||||||
protocolSettings.packetHandler.registerPacket(new MessagePacket());
|
else
|
||||||
|
protocolServer.attachBridge(this, protocolValues.keyPass, protocolValues.ssl, protocolValues.authMode);
|
||||||
|
|
||||||
instance = this;
|
initializeProtocolVersion();
|
||||||
|
downloadLicenses();
|
||||||
|
|
||||||
|
// Register the appropriate listeners and packets
|
||||||
|
registerListeners();
|
||||||
|
registerPackets();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProtocolBridge(ProtocolVersion protocolVersion, ProtocolSettings protocolSettings, ProtocolServer protocolServer, APIInformation apiInformation) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
|
/**
|
||||||
this(protocolVersion, protocolSettings, protocolServer);
|
* Initialize the ProtocolBridge instance for the client side
|
||||||
this.apiInformation = apiInformation;
|
*
|
||||||
instance = this;
|
* @param protocolClient The ProtocolClient instance
|
||||||
|
* @param protocolValues The ProtocolSettings instance
|
||||||
|
* @throws Exception if an error occurs while initializing the ProtocolBridge
|
||||||
|
*/
|
||||||
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.CLIENT)
|
||||||
|
public ProtocolBridge(ProtocolClient protocolClient, LibClientImpl_v1_0_1_B libClientImpl,
|
||||||
|
ProtocolValues protocolValues) throws Exception {
|
||||||
|
// Assign the parameters to the class fields
|
||||||
|
this.protocolClient = protocolClient;
|
||||||
|
this.protocolValues = protocolValues;
|
||||||
|
|
||||||
|
protocolClient.attachBridge(this);
|
||||||
|
initializeProtocolVersion();
|
||||||
|
downloadLicenses();
|
||||||
|
|
||||||
|
|
||||||
|
// Register the appropriate listeners and packets
|
||||||
|
registerListeners();
|
||||||
|
registerPackets();
|
||||||
|
installUrl(libClientImpl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final void checkUpdates() {
|
private void installUrl(LibClientImpl_v1_0_1_B libClientImpl) {
|
||||||
try {
|
if (protocolValues.protocolVersion == ProtocolVersion.PV_1_0_0_BETA) {
|
||||||
URL oracle = new URL("https://raw.githubusercontent.com/Open-Autonomous-Connection/Protocol/master/src/main/java/me/openautonomousconnection/protocol/version.txt");
|
OacWebUrlInstaller_v1_0_0_B.installOnce(this, libClientImpl);
|
||||||
|
}
|
||||||
|
|
||||||
BufferedReader in = new BufferedReader(new InputStreamReader(oracle.openStream()));
|
if (protocolValues.protocolVersion == ProtocolVersion.PV_1_0_1_BETA) {
|
||||||
String version = "";
|
OacUrlHandlerInstaller_v1_0_1_B.installOnce(this, libClientImpl, libClientImpl, libClientImpl);
|
||||||
String inputLine;
|
|
||||||
while ((inputLine = in.readLine()) != null) version += inputLine;
|
|
||||||
|
|
||||||
if (!version.equalsIgnoreCase(Files.readString(new File("version.txt").toPath()))) {
|
|
||||||
System.out.println();
|
|
||||||
System.out.println("========================================================");
|
|
||||||
System.out.println("IMPORTANT: A NEW PROTOCOL VERSION IS PUBLISHED ON GITHUB");
|
|
||||||
System.out.println("========================================================");
|
|
||||||
System.out.println();
|
|
||||||
}
|
|
||||||
} catch (IOException exception) {
|
|
||||||
System.out.println();
|
|
||||||
System.out.println("=======================================================================");
|
|
||||||
System.out.println("IMPORTANT: PROTOCOL VERSION CHECK COULD NOT COMPLETED! VISIT OUR GITHUB");
|
|
||||||
System.out.println(" https://github.com/Open-Autonomous-Connection ");
|
|
||||||
System.out.println("=======================================================================");
|
|
||||||
System.out.println();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final APIInformation getApiInformation() {
|
private void downloadLicenses() throws IOException {
|
||||||
return apiInformation;
|
File licensesFolder = new File("licenses");
|
||||||
|
|
||||||
|
if (!licensesFolder.exists() || !licensesFolder.isDirectory()) {
|
||||||
|
if (licensesFolder.exists()) licensesFolder.delete();
|
||||||
|
|
||||||
|
File output = new File("licenses.zip");
|
||||||
|
output.createNewFile();
|
||||||
|
FileUtils.downloadFile("https://open-autonomous-connection.org/assets/licenses.zip", output);
|
||||||
|
FileUtils.unzip(output, licensesFolder.getParent());
|
||||||
|
output.deleteOnExit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the appropriate packets
|
||||||
|
*
|
||||||
|
* <p>Overview of all Packets
|
||||||
|
*
|
||||||
|
* <table border="2">
|
||||||
|
* <tr><th>ID</th><th>Packet</th><th>ProtocolVersion</th></tr>
|
||||||
|
*
|
||||||
|
* <tr><td>8</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.AuthPacket AuthPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
|
||||||
|
* <tr><td>7</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket INSQueryPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
|
||||||
|
* <tr><td>6</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSResponsePacket INSResponsePacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
|
||||||
|
* <tr><td>10</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket WebRequestPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
|
||||||
|
* <tr><td>9</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket WebResponsePacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
|
||||||
|
* <tr><td>11</td><td>{@link WebStreamStartPacket_v1_0_0_B v1_0_0.beta.web.stream.WebStreamStartPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
|
||||||
|
* <tr><td>13</td><td>{@link WebStreamChunkPacket_v1_0_0_B v1_0_0.beta.web.stream.WebStreamChunkPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
|
||||||
|
* <tr><td>12</td><td>{@link WebStreamEndPacket_v1_0_0_B v1_0_0.beta.web.stream.WebStreamEndPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
|
||||||
|
*
|
||||||
|
* <tr><td>1</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateRequestPacket WebNavigateRequestPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>2</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateAckPacket WebNavigateAckPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>3</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket WebResourceRequestPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>4</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket WebResourceResponsePacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>14</td><td>{@link WebStreamStartPacket_v1_0_1_B v1_0_1.beta.web.impl.stream.WebStreamStartPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>15</td><td>{@link WebStreamChunkPacket_v1_0_1_B v1_0_1.beta.web.impl.stream.WebStreamChunkPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>16</td><td>{@link WebStreamEndPacket_v1_0_1_B v1_0_1.beta.web.impl.stream.WebStreamEndPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>17</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentSnapshotEventPacket WebDocumentSnapshotEventPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>5</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyResponsePacket WebDocumentApplyResponsePacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* <tr><td>18</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyRequestPacket WebDocumentApplyRequestPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
|
||||||
|
* </table>
|
||||||
|
*
|
||||||
|
* @see org.openautonomousconnection.protocol.versions.ProtocolVersion
|
||||||
|
*/
|
||||||
|
private void registerPackets() {
|
||||||
|
// 1.0.0-BETA packets
|
||||||
|
registerPacket(() -> new AuthPacket(this));
|
||||||
|
registerPacket(INSQueryPacket::new);
|
||||||
|
registerPacket(() -> new INSResponsePacket(this));
|
||||||
|
registerPacket(WebRequestPacket::new);
|
||||||
|
registerPacket(WebResponsePacket::new);
|
||||||
|
registerPacket(WebStreamStartPacket_v1_0_0_B::new);
|
||||||
|
registerPacket(WebStreamChunkPacket_v1_0_0_B::new);
|
||||||
|
registerPacket(WebStreamEndPacket_v1_0_0_B::new);
|
||||||
|
|
||||||
|
// 1.0.1-BETA Packets
|
||||||
|
registerPacket(() -> new WebDocumentApplyRequestPacket(this));
|
||||||
|
registerPacket(WebDocumentApplyResponsePacket::new);
|
||||||
|
registerPacket(WebDocumentSnapshotEventPacket::new);
|
||||||
|
registerPacket(() -> new WebNavigateRequestPacket(this));
|
||||||
|
registerPacket(WebNavigateAckPacket::new);
|
||||||
|
registerPacket(() -> new WebResourceRequestPacket(this));
|
||||||
|
registerPacket(WebResourceResponsePacket::new);
|
||||||
|
registerPacket(WebStreamStartPacket_v1_0_1_B::new);
|
||||||
|
registerPacket(WebStreamChunkPacket_v1_0_1_B::new);
|
||||||
|
registerPacket(WebStreamEndPacket_v1_0_1_B::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerPacket(Supplier<? extends OACPacket> factory) {
|
||||||
|
if (isPacketSupported(factory.get())) protocolValues.packetHandler.registerPacket(factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the appropriate listeners based on the current side
|
||||||
|
*
|
||||||
|
* @throws Exception if an error occurs while registering the listeners
|
||||||
|
*/
|
||||||
|
private void registerListeners() throws Exception {
|
||||||
|
// Client Listeners
|
||||||
|
if (isRunningAsClient()) {
|
||||||
|
protocolValues.eventManager.registerListener(new ClientListener(protocolClient));
|
||||||
|
protocolValues.eventManager.unregisterListener(CustomServerListener.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server Listeners
|
||||||
|
if (isRunningAsServer()) {
|
||||||
|
protocolValues.eventManager.registerListener(new CustomServerListener(protocolServer));
|
||||||
|
protocolValues.eventManager.unregisterListener(ClientListener.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the protocol version
|
||||||
|
* Validate if the protocol version is valid for the current side
|
||||||
|
* If not, log an error and exit the application
|
||||||
|
*/
|
||||||
|
private void initializeProtocolVersion() {
|
||||||
|
// Check if the protocol version is valid for the current side
|
||||||
|
// If not, log an error and exit the application
|
||||||
|
if (!validateProtocolSide()) {
|
||||||
|
protocolValues.logger.error("Invalid protocol version '" + protocolValues.protocolVersion.toString() + "'!");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the classic protocol is supported by the current protocol version
|
||||||
|
*
|
||||||
|
* @return true if the classic protocol is supported, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isClassicSupported() {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : protocolValues.protocolVersion.getCompatibleVersions()) {
|
||||||
|
// Check if the compatible version is classic
|
||||||
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the current protocol version is classic or if it is supported by any of the compatible versions
|
||||||
|
return protocolValues.protocolVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC || yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the target protocol is supported by the current protocol version
|
||||||
|
*
|
||||||
|
* @param protocol The target protocol to check
|
||||||
|
* @return true If the target protocol is supported, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isProtocolSupported(ProtocolVersion.Protocol protocol) {
|
||||||
|
boolean yes = false;
|
||||||
|
|
||||||
|
for (ProtocolVersion compatibleVersion : protocolValues.protocolVersion.getCompatibleVersions()) {
|
||||||
|
// Check if the compatible version supports the target protocol
|
||||||
|
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the current protocol version supports the target protocol or if it is supported by any of the compatible versions
|
||||||
|
return protocolValues.protocolVersion.getSupportedProtocols().contains(protocol) || yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the target packet is supported by the current protocol version
|
||||||
|
*
|
||||||
|
* @param packet The target packet to check
|
||||||
|
* @return true if the target packet is supported, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isPacketSupported(OACPacket packet) {
|
||||||
|
boolean compatible = false;
|
||||||
|
|
||||||
|
for (ProtocolVersion compatibleVersion : packet.getCompatibleVersions()) {
|
||||||
|
if (!compatible) compatible = isVersionSupported(compatibleVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
return compatible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the target protocol version is supported by the current protocol version
|
||||||
|
*
|
||||||
|
* @param targetVersion The target protocol version to check
|
||||||
|
* @return true if the target protocol version is supported, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isVersionSupported(ProtocolVersion targetVersion) {
|
||||||
|
// Check if the target protocol version is the same as the current protocol version or if it is in the list of compatible versions
|
||||||
|
return protocolValues.protocolVersion == targetVersion || protocolValues.protocolVersion.getCompatibleVersions().contains(targetVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate if the protocol version is valid for the current side
|
||||||
|
*
|
||||||
|
* @return true if the protocol version is valid for the current side, false otherwise
|
||||||
|
*/
|
||||||
|
private boolean validateProtocolSide() {
|
||||||
|
return
|
||||||
|
(isRunningAsClient() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT) ||
|
||||||
|
(isRunningAsClient() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_WEB) ||
|
||||||
|
(isRunningAsClient() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_INS) ||
|
||||||
|
(isRunningAsClient() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL) ||
|
||||||
|
|
||||||
|
(isRunningAsWebServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.WEB) ||
|
||||||
|
(isRunningAsWebServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_WEB) ||
|
||||||
|
(isRunningAsWebServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.WEB_INS) ||
|
||||||
|
(isRunningAsWebServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL) ||
|
||||||
|
|
||||||
|
(isRunningAsServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL) ||
|
||||||
|
|
||||||
|
(isRunningAsINSServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.INS) ||
|
||||||
|
(isRunningAsINSServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.WEB_INS) ||
|
||||||
|
(isRunningAsINSServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_INS) ||
|
||||||
|
(isRunningAsINSServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current instance is running as a INS server
|
||||||
|
*
|
||||||
|
* @return true if the current instance is running as a INS server, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isRunningAsINSServer() {
|
||||||
|
return isRunningAsServer() && protocolServer instanceof ProtocolINSServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current instance is running as a client
|
||||||
|
*
|
||||||
|
* @return true if the current instance is running as a client, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isRunningAsClient() {
|
||||||
|
return protocolClient != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current instance is running as a web server
|
||||||
|
*
|
||||||
|
* @return true if the current instance is running as a web server, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isRunningAsWebServer() {
|
||||||
|
if (protocolValues.protocolVersion == ProtocolVersion.PV_1_0_0_BETA)
|
||||||
|
return isRunningAsServer() && protocolServer instanceof ProtocolWebServer_1_0_0_B;
|
||||||
|
return isRunningAsServer() && protocolServer instanceof ProtocolWebServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current instance is running as a custom server
|
||||||
|
*
|
||||||
|
* @return true if the current instance is running as a custom server, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isRunningAsServer() {
|
||||||
|
return protocolServer != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
|
|
||||||
|
|
||||||
public class ProtocolSettings extends DefaultMethodsOverrider {
|
|
||||||
|
|
||||||
public String host = "45.155.173.50";
|
|
||||||
public int port = 8345;
|
|
||||||
public PacketHandler packetHandler = new PacketHandler();
|
|
||||||
public EventManager eventManager = new EventManager();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
public enum ProtocolVersion implements Serializable {
|
|
||||||
PV_1_0_0("1.0.0");
|
|
||||||
;
|
|
||||||
public final String version;
|
|
||||||
|
|
||||||
ProtocolVersion(String version) {
|
|
||||||
this.version = version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.openautonomousconnection.protocol.annotations;
|
||||||
|
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation to provide metadata about protocol handlers or classes.
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.5")
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface ProtocolInfo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the side of the protocol that the annotated class or method is associated with.
|
||||||
|
* Default is ALL, indicating that it can be used on any side.
|
||||||
|
*
|
||||||
|
* @return The protocol side.
|
||||||
|
*/
|
||||||
|
ProtocolVersion.ProtocolSide protocolSide() default ProtocolVersion.ProtocolSide.ALL;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package org.openautonomousconnection.protocol.annotations.processing;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.reflections.GenericReflectClass;
|
||||||
|
import net.bytebuddy.agent.ByteBuddyAgent;
|
||||||
|
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.instrument.Instrumentation;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.any;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
|
|
||||||
|
public class CallTracker<A extends Annotation> extends GenericReflectClass<A> {
|
||||||
|
private static final AtomicReference<Class<? extends Annotation>> atomicClass = new AtomicReference<>();
|
||||||
|
|
||||||
|
public CallTracker(CallInterceptor interceptor) {
|
||||||
|
super();
|
||||||
|
atomicClass.set(this.persistentClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void premain(String agentArgs, Instrumentation inst) {
|
||||||
|
ByteBuddyAgent.install();
|
||||||
|
|
||||||
|
new AgentBuilder.Default()
|
||||||
|
.type(any()) // instrument all classes, you can restrict here
|
||||||
|
.transform((builder, type, classLoader, module, protectionDomain) ->
|
||||||
|
builder.visit(Advice.to(CallInterceptor.class).on(isMethod()))).installOn(inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class CallInterceptor {
|
||||||
|
private static final Set<CallInterceptor> interceptors = new HashSet<>();
|
||||||
|
|
||||||
|
public CallInterceptor() {
|
||||||
|
interceptors.add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Advice.OnMethodEnter
|
||||||
|
static void intercept(@Advice.Origin Method method) {
|
||||||
|
for (CallInterceptor interceptor : interceptors) {
|
||||||
|
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
||||||
|
|
||||||
|
if (stack.length <= 3) return;
|
||||||
|
|
||||||
|
StackTraceElement caller = stack[3];
|
||||||
|
interceptor.onCall(method, caller);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (method.isAnnotationPresent(atomicClass.get())) {
|
||||||
|
// StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
||||||
|
// // stack[0] = getStackTrace
|
||||||
|
// // stack[1] = intercept
|
||||||
|
// // stack[2] = Advice dispatcher
|
||||||
|
// // stack[3+] = your actual caller
|
||||||
|
// if (stack.length <= 3)
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// StackTraceElement caller = stack[3];
|
||||||
|
//
|
||||||
|
// System.out.println("Annotated method " + method.getName()
|
||||||
|
// + " was called by " + caller.getClassName() + "." + caller.getMethodName());
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Code executed on any method call
|
||||||
|
*/
|
||||||
|
public abstract void onCall(Method method, StackTraceElement callerMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
// Author: maple
|
||||||
|
// date: 9/29/25
|
||||||
|
|
||||||
|
package org.openautonomousconnection.protocol.annotations.processing;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.reflections.annotation.processing.AnnotationProcessor;
|
||||||
|
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||||
|
import org.openautonomousconnection.protocol.exceptions.IncompatibleProtocolSideException;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process ProtocolInfo annotation and throw exception on mismatching annotation and ProtocolSide
|
||||||
|
*/
|
||||||
|
public class ProtocolInfoProcessing extends AnnotationProcessor<ProtocolInfo> {
|
||||||
|
|
||||||
|
private final CallTracker<ProtocolInfo> tracker;
|
||||||
|
|
||||||
|
private final AtomicReference<Set<Method>> methodReferences = new AtomicReference<>();
|
||||||
|
private final AtomicReference<Set<Class<?>>> typeReferences = new AtomicReference<>();
|
||||||
|
|
||||||
|
public ProtocolInfoProcessing() {
|
||||||
|
super("org.openautonomousconnection.protocol");
|
||||||
|
|
||||||
|
this.process();
|
||||||
|
|
||||||
|
this.methodReferences.set(this.annotatedMethods);
|
||||||
|
|
||||||
|
this.typeReferences.set(this.annotatedTypes);
|
||||||
|
|
||||||
|
this.tracker = new CallTracker<>(new CallTracker.CallInterceptor() {
|
||||||
|
@Override
|
||||||
|
public void onCall(Method method, StackTraceElement callerMethod) {
|
||||||
|
ProtocolVersion.ProtocolSide side, callerSide;
|
||||||
|
Object o;
|
||||||
|
|
||||||
|
if ((o = methodGetByName(callerMethod.getMethodName())) != null)
|
||||||
|
callerSide = ((Method) o).getAnnotation(ProtocolInfo.class).protocolSide();
|
||||||
|
else if ((o = typeHasAnnotation(callerMethod.getClassName())) != null)
|
||||||
|
callerSide = ((Class<?>) o).getAnnotation(ProtocolInfo.class).protocolSide();
|
||||||
|
else return;
|
||||||
|
|
||||||
|
|
||||||
|
if (methodReferences.get().contains(method))
|
||||||
|
side = method.getAnnotation(ProtocolInfo.class).protocolSide();
|
||||||
|
else if (typeReferences.get().contains(method.getDeclaringClass()))
|
||||||
|
side = method.getDeclaringClass().getAnnotation(ProtocolInfo.class).protocolSide();
|
||||||
|
else return;
|
||||||
|
|
||||||
|
if (callerSide.equals(ProtocolVersion.ProtocolSide.CLIENT) && !side.equals(callerSide))
|
||||||
|
throw new IncompatibleProtocolSideException(callerSide, side);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method methodGetByName(String methodName) {
|
||||||
|
for (Method method : this.annotatedMethods) if (method.getName().equals(methodName)) return method;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class<?> typeHasAnnotation(String typeName) {
|
||||||
|
for (Class<?> type : this.annotatedTypes) if (type.getName().equals(typeName)) return type;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processType(Class<?> type) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processMethod(Method method) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processField(Field field) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processConstructor(Constructor constructor) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol.domain;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.string.StringUtils;
|
|
||||||
import org.openautonomousconnection.protocol.utils.DomainUtils;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
public class Domain implements Serializable {
|
|
||||||
public final String name;
|
|
||||||
public final String topLevelDomain;
|
|
||||||
private final String destination;
|
|
||||||
private final String path;
|
|
||||||
|
|
||||||
public Domain(String name, String topLevelDomain, String destination, String path) {
|
|
||||||
if (path == null) path = "";
|
|
||||||
|
|
||||||
this.name = name;
|
|
||||||
this.topLevelDomain = topLevelDomain;
|
|
||||||
this.destination = destination;
|
|
||||||
this.path = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final String realDestination() {
|
|
||||||
String tmpDestination = destination.endsWith("/") ? destination : destination + "/";
|
|
||||||
String tmpPath = getPath();
|
|
||||||
|
|
||||||
if (tmpPath == null) tmpPath = "";
|
|
||||||
if (tmpPath.startsWith("/")) tmpPath = tmpPath.substring("/".length());
|
|
||||||
if (tmpPath.endsWith("/")) tmpPath = tmpPath.substring(0, tmpPath.length() - "/".length());
|
|
||||||
|
|
||||||
return tmpDestination + tmpPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final String getPath() {
|
|
||||||
if (path.endsWith("/")) return path.substring(0, path.length() - "/".length());
|
|
||||||
if (path.startsWith("/")) return path.substring("/".length());
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final String parsedDestination() {
|
|
||||||
if (destination.toLowerCase().startsWith("https://github.com/")) {
|
|
||||||
String base = "https://raw.githubusercontent.com/";
|
|
||||||
String username = DomainUtils.getPath(destination).split("/")[0];
|
|
||||||
String site = DomainUtils.getPath(destination).split("/")[1];
|
|
||||||
|
|
||||||
String tmpPath = getPath();
|
|
||||||
if (tmpPath == null || StringUtils.isEmptyString(tmpPath)) tmpPath = "index.html";
|
|
||||||
if (tmpPath.startsWith("/")) tmpPath = tmpPath.substring("/".length());
|
|
||||||
if (tmpPath.endsWith("/")) tmpPath = tmpPath.substring(0, tmpPath.length() - "/".length());
|
|
||||||
|
|
||||||
base = base + username + "/" + site + "/main/" + tmpPath;
|
|
||||||
return base;
|
|
||||||
}
|
|
||||||
|
|
||||||
return realDestination();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final Object clone() throws CloneNotSupportedException {
|
|
||||||
return new Domain(name, topLevelDomain, destination, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final boolean equals(Object obj) {
|
|
||||||
if (!(obj instanceof Domain other)) return false;
|
|
||||||
return other.name.equalsIgnoreCase(name) && other.topLevelDomain.equalsIgnoreCase(topLevelDomain);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final String toString() {
|
|
||||||
return "{parsed='" + parsedDestination() + "';real='" + realDestination() + "'}";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final int hashCode() {
|
|
||||||
return super.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol.domain;
|
|
||||||
|
|
||||||
public class LocalDomain extends Domain {
|
|
||||||
public LocalDomain(String name, String endName, String path) {
|
|
||||||
super(name, endName, null, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol.domain;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
public class RequestDomain extends Domain implements Serializable {
|
|
||||||
|
|
||||||
public RequestDomain(String name, String topLevelDomain, String path) {
|
|
||||||
super(name, topLevelDomain, null, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol.events.v1_0_0;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolVersion;
|
|
||||||
import org.openautonomousconnection.protocol.domain.Domain;
|
|
||||||
import org.openautonomousconnection.protocol.domain.RequestDomain;
|
|
||||||
|
|
||||||
public class DomainPacketReceivedEvent extends Event {
|
|
||||||
|
|
||||||
public final ProtocolVersion protocolVersion;
|
|
||||||
public final Domain domain;
|
|
||||||
public final RequestDomain requestDomain;
|
|
||||||
public final int clientID;
|
|
||||||
|
|
||||||
public DomainPacketReceivedEvent(ProtocolVersion protocolVersion, Domain domain, RequestDomain requestDomain, int clientID) {
|
|
||||||
this.protocolVersion = protocolVersion;
|
|
||||||
this.domain = domain;
|
|
||||||
this.requestDomain = requestDomain;
|
|
||||||
this.clientID = clientID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final Object clone() throws CloneNotSupportedException {
|
|
||||||
return super.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final boolean equals(Object obj) {
|
|
||||||
return super.equals(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final String toString() {
|
|
||||||
return super.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final int hashCode() {
|
|
||||||
return super.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol.events.v1_0_0;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolVersion;
|
|
||||||
import org.openautonomousconnection.protocol.domain.Domain;
|
|
||||||
import org.openautonomousconnection.protocol.domain.RequestDomain;
|
|
||||||
|
|
||||||
public class PingPacketReceivedEvent extends Event {
|
|
||||||
|
|
||||||
public final ProtocolVersion protocolVersion;
|
|
||||||
public final Domain domain;
|
|
||||||
public final RequestDomain requestDomain;
|
|
||||||
public final boolean reachable;
|
|
||||||
public final int clientID;
|
|
||||||
|
|
||||||
public PingPacketReceivedEvent(ProtocolVersion protocolVersion, Domain domain, RequestDomain requestDomain, boolean reachable, int clientID) {
|
|
||||||
this.protocolVersion = protocolVersion;
|
|
||||||
this.domain = domain;
|
|
||||||
this.requestDomain = requestDomain;
|
|
||||||
this.reachable = reachable;
|
|
||||||
this.clientID = clientID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final Object clone() throws CloneNotSupportedException {
|
|
||||||
return super.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final boolean equals(Object obj) {
|
|
||||||
return super.equals(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final String toString() {
|
|
||||||
return super.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final int hashCode() {
|
|
||||||
return super.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.openautonomousconnection.protocol.exceptions;
|
||||||
|
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when an unsupported protocol side method is called.
|
||||||
|
*/
|
||||||
|
public class IncompatibleProtocolSideException extends RuntimeException {
|
||||||
|
public IncompatibleProtocolSideException(ProtocolVersion.ProtocolSide callerSide, ProtocolVersion.ProtocolSide side) {
|
||||||
|
super(callerSide.name() + " is incompatible with called method of ProtocolSide " + side.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package org.openautonomousconnection.protocol.exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when an unsupported protocol is encountered.
|
||||||
|
*/
|
||||||
|
public final class UnsupportedProtocolException extends RuntimeException {
|
||||||
|
|
||||||
|
public UnsupportedProtocolException() {
|
||||||
|
this("Selected protocol is not supported!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnsupportedProtocolException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnsupportedProtocolException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnsupportedProtocolException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnsupportedProtocolException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||||
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,89 +1,51 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol.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 org.openautonomousconnection.protocol.ProtocolBridge;
|
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientConnectedEvent;
|
||||||
import org.openautonomousconnection.protocol.domain.LocalDomain;
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
import org.openautonomousconnection.protocol.events.v1_0_0.DomainPacketReceivedEvent;
|
import lombok.Getter;
|
||||||
import org.openautonomousconnection.protocol.events.v1_0_0.PingPacketReceivedEvent;
|
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.PingPacket;
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.AuthPacket;
|
||||||
import org.openautonomousconnection.protocol.utils.SiteType;
|
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
|
||||||
import org.openautonomousconnection.protocol.utils.WebsitesContent;
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
/**
|
||||||
import java.io.IOException;
|
* Listener for client-side events such as connection and disconnection.
|
||||||
import java.io.InputStreamReader;
|
*/
|
||||||
import java.net.HttpURLConnection;
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.CLIENT)
|
||||||
import java.net.URL;
|
public final class ClientListener extends EventListener {
|
||||||
|
|
||||||
public class ClientListener extends EventListener {
|
/**
|
||||||
|
* The reference to the ProtocolClient object
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final ProtocolClient client;
|
||||||
|
|
||||||
@Listener
|
/**
|
||||||
public void onDomain(DomainPacketReceivedEvent event) {
|
* Sets the client variable
|
||||||
boolean exists = event.domain != null;
|
*
|
||||||
|
* @param client The Instance of the ProtocolClient
|
||||||
if (exists) {
|
*/
|
||||||
try {
|
public ClientListener(ProtocolClient client) {
|
||||||
if (!ProtocolBridge.getInstance().getProtocolClient().getClient().sendPacket(new PingPacket(event.requestDomain, event.domain, false))) {
|
this.client = client;
|
||||||
ProtocolBridge.getInstance().getProtocolClient().handleHTMLContent(SiteType.PROTOCOL, new LocalDomain("error-occurred", "html", ""),
|
|
||||||
WebsitesContent.ERROR_OCCURRED(event.domain.toString() + "/" + event.domain.getPath()));
|
|
||||||
}
|
|
||||||
} catch (IOException | ClassNotFoundException e) {
|
|
||||||
ProtocolBridge.getInstance().getProtocolClient().handleHTMLContent(SiteType.PROTOCOL, new LocalDomain("error-occurred", "html", ""),
|
|
||||||
WebsitesContent.ERROR_OCCURRED(event.domain.toString() + "/" + event.domain.getPath() + "\n" + e.getMessage()));
|
|
||||||
}
|
|
||||||
} else ProtocolBridge.getInstance().getProtocolClient().handleHTMLContent(SiteType.PROTOCOL, new LocalDomain("domain-not-found", "html", ""), WebsitesContent.DOMAIN_NOT_FOUND);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Listener
|
/**
|
||||||
public void onPing(PingPacketReceivedEvent event) {
|
* Handles the event when a client connects.
|
||||||
if (event.reachable) {
|
* Sends an authentication packet to the server.
|
||||||
String destination = event.domain.parsedDestination();
|
*
|
||||||
|
* @param event The client connected event.
|
||||||
try {
|
*/
|
||||||
URL url = new URL(destination);
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
HttpURLConnection connection2 = (HttpURLConnection) url.openConnection();
|
public void onConnect(ClientConnectedEvent event) {
|
||||||
connection2.setRequestMethod("GET");
|
try {
|
||||||
|
event.getClient().sendPacket(new AuthPacket(client.getProtocolBridge()), TransportProtocol.TCP);
|
||||||
StringBuilder content = new StringBuilder();
|
} catch (Exception exception) {
|
||||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection2.getInputStream()))) {
|
client.getProtocolBridge().getProtocolValues().logger.exception("Failed to send auth packet", exception);
|
||||||
String line;
|
event.getClient().disconnect();
|
||||||
while ((line = reader.readLine()) != null) content.append(line);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ProtocolBridge.getInstance().getProtocolClient().handleHTMLContent(SiteType.PUBLIC, event.domain, content.toString());
|
|
||||||
} catch (IOException exception) {
|
|
||||||
ProtocolBridge.getInstance().getProtocolClient().handleHTMLContent(SiteType.PROTOCOL, new LocalDomain("error-occurred", "html", ""),
|
|
||||||
WebsitesContent.ERROR_OCCURRED(exception.getMessage().replace(event.domain.parsedDestination(), event.domain.toString() + "/" + event.domain.getPath())));
|
|
||||||
}
|
|
||||||
} else ProtocolBridge.getInstance().getProtocolClient().handleHTMLContent(SiteType.PROTOCOL, new LocalDomain("error-not-reached", "html", ""), WebsitesContent.DOMAIN_NOT_REACHABLE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final Object clone() throws CloneNotSupportedException {
|
|
||||||
return super.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final boolean equals(Object obj) {
|
|
||||||
return super.equals(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final String toString() {
|
|
||||||
return super.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final int hashCode() {
|
|
||||||
return super.hashCode();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol.listeners;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
|
||||||
|
|
||||||
public class ServerListener extends EventListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final Object clone() throws CloneNotSupportedException {
|
|
||||||
return super.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final boolean equals(Object obj) {
|
|
||||||
return super.equals(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final String toString() {
|
|
||||||
return super.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final int hashCode() {
|
|
||||||
return super.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class representing a packet in the Open Autonomous Connection (OAC) protocol.
|
||||||
|
* This class extends the base Packet class and includes additional functionality specific to OAC.
|
||||||
|
*/
|
||||||
|
public abstract class OACPacket extends Packet {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The protocol version associated with this packet.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final List<ProtocolVersion> compatibleVersions;
|
||||||
|
private final int id;
|
||||||
|
/**
|
||||||
|
* The response code for the packet, defaulting to RESPONSE_NOT_REQUIRED.
|
||||||
|
*/
|
||||||
|
private INSResponseStatus responseCode = INSResponseStatus.RESPONSE_NOT_REQUIRED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for OACPacket.
|
||||||
|
*
|
||||||
|
* @param id The unique identifier for the packet.
|
||||||
|
* @param supportedVersions The protocol version associated with this packet.
|
||||||
|
*/
|
||||||
|
public OACPacket(int id, ProtocolVersion... supportedVersions) {
|
||||||
|
this.id = id;
|
||||||
|
this.compatibleVersions = List.of(supportedVersions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPacketID() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the response code for the packet.
|
||||||
|
*
|
||||||
|
* @return The INSResponseCode associated with the packet.
|
||||||
|
*/
|
||||||
|
protected final INSResponseStatus getResponseCode() {
|
||||||
|
return responseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the response code for the packet.
|
||||||
|
*
|
||||||
|
* @param responseCode The INSResponseCode to set for the packet.
|
||||||
|
*/
|
||||||
|
protected final void setResponseCode(INSResponseStatus responseCode) {
|
||||||
|
this.responseCode = responseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the packet data to the output stream.
|
||||||
|
*
|
||||||
|
* @param outputStream The output stream to write the packet data to.
|
||||||
|
* @throws IOException If an I/O error occurs.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public final void write(DataOutputStream outputStream) throws IOException {
|
||||||
|
// Write the specific packet data
|
||||||
|
onWrite(outputStream);
|
||||||
|
|
||||||
|
// Write the response code if the protocol version is not classic
|
||||||
|
if (!compatibleVersions.contains(ProtocolVersion.PV_1_0_0_CLASSIC)) outputStream.writeUTF(responseCode.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void read(DataInputStream inputStream, UUID clientID) throws IOException {
|
||||||
|
// Read the specific packet data
|
||||||
|
onRead(inputStream, clientID);
|
||||||
|
|
||||||
|
// Read the response code if the protocol version is not classic
|
||||||
|
if (!compatibleVersions.contains(ProtocolVersion.PV_1_0_0_CLASSIC))
|
||||||
|
responseCode = INSResponseStatus.valueOf(inputStream.readUTF());
|
||||||
|
else responseCode = INSResponseStatus.RESPONSE_NOT_REQUIRED;
|
||||||
|
|
||||||
|
// Call the response code read handler
|
||||||
|
onResponseCodeRead(inputStream, clientID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract method to be implemented by subclasses for writing specific packet data.
|
||||||
|
*
|
||||||
|
* @param outputStream The output stream to write the packet data to.
|
||||||
|
* @throws IOException If an I/O error occurs.
|
||||||
|
*/
|
||||||
|
public abstract void onWrite(DataOutputStream outputStream) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract method to be implemented by subclasses for reading specific packet data.
|
||||||
|
*
|
||||||
|
* @param inputStream The input stream to read the packet data from.
|
||||||
|
* @throws IOException If an I/O error occurs.
|
||||||
|
*/
|
||||||
|
public abstract void onRead(DataInputStream inputStream, UUID clientID) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called after the response code has been read from the input stream.
|
||||||
|
* Subclasses can override this method to handle any additional logic based on the response code.
|
||||||
|
*
|
||||||
|
* @param inputStream The input stream from which the response code was read.
|
||||||
|
*/
|
||||||
|
protected void onResponseCodeRead(DataInputStream inputStream, UUID clientID) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a string map in a deterministic way (no Java object serialization).
|
||||||
|
*
|
||||||
|
* @param out output stream
|
||||||
|
* @param map map to write (may be null)
|
||||||
|
* @throws IOException on I/O errors
|
||||||
|
*/
|
||||||
|
protected final void writeStringMap(DataOutputStream out, Map<String, String> map) throws IOException {
|
||||||
|
if (map == null || map.isEmpty()) {
|
||||||
|
out.writeInt(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.writeInt(map.size());
|
||||||
|
for (Map.Entry<String, String> e : map.entrySet()) {
|
||||||
|
// Null keys/values are normalized to empty strings to keep the wire format stable.
|
||||||
|
out.writeUTF((e.getKey() != null) ? e.getKey() : "");
|
||||||
|
out.writeUTF((e.getValue() != null) ? e.getValue() : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a string map in a deterministic way (no Java object serialization).
|
||||||
|
*
|
||||||
|
* @param in input stream
|
||||||
|
* @return headers map (never null)
|
||||||
|
* @throws IOException on I/O errors / invalid sizes
|
||||||
|
*/
|
||||||
|
protected final Map<String, String> readStringMap(DataInputStream in) throws IOException {
|
||||||
|
int size = in.readInt();
|
||||||
|
if (size < 0) {
|
||||||
|
throw new IOException("Negative map size");
|
||||||
|
}
|
||||||
|
if (size == 0) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> map = new LinkedHashMap<>(Math.max(16, size * 2));
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
String key = in.readUTF();
|
||||||
|
String value = in.readUTF();
|
||||||
|
map.put(key, value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol.packets.v1_0_0;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolVersion;
|
|
||||||
import org.openautonomousconnection.protocol.domain.Domain;
|
|
||||||
import org.openautonomousconnection.protocol.domain.RequestDomain;
|
|
||||||
import org.openautonomousconnection.protocol.events.v1_0_0.DomainPacketReceivedEvent;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
public class DomainPacket extends Packet {
|
|
||||||
private int clientID;
|
|
||||||
private RequestDomain requestDomain;
|
|
||||||
private Domain domain;
|
|
||||||
private ProtocolVersion protocolVersion;
|
|
||||||
|
|
||||||
public DomainPacket(RequestDomain requestDomain, Domain domain) {
|
|
||||||
this();
|
|
||||||
|
|
||||||
this.requestDomain = requestDomain;
|
|
||||||
this.domain = domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DomainPacket() {
|
|
||||||
super(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
|
|
||||||
protocolVersion = ProtocolBridge.getInstance().getProtocolVersion();
|
|
||||||
|
|
||||||
if (ProtocolBridge.getInstance().isRunningAsServer()) {
|
|
||||||
objectOutputStream.writeInt(clientID);
|
|
||||||
objectOutputStream.writeObject(requestDomain);
|
|
||||||
objectOutputStream.writeObject(domain);
|
|
||||||
} else {
|
|
||||||
clientID = ProtocolBridge.getInstance().getProtocolClient().getClient().getClientID();
|
|
||||||
objectOutputStream.writeInt(clientID);
|
|
||||||
objectOutputStream.writeObject(requestDomain);
|
|
||||||
}
|
|
||||||
|
|
||||||
objectOutputStream.writeObject(protocolVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void read(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
|
|
||||||
if (ProtocolBridge.getInstance().isRunningAsServer()) {
|
|
||||||
clientID = objectInputStream.readInt();
|
|
||||||
requestDomain = (RequestDomain) objectInputStream.readObject();
|
|
||||||
protocolVersion = (ProtocolVersion) objectInputStream.readObject();
|
|
||||||
|
|
||||||
try {
|
|
||||||
domain = ProtocolBridge.getInstance().getProtocolServer().getDomain(requestDomain);
|
|
||||||
} catch (SQLException exception) {
|
|
||||||
exception.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
ProtocolBridge.getInstance().getProtocolServer().getServer().getEventManager().executeEvent(new DomainPacketReceivedEvent(protocolVersion, domain, requestDomain, clientID));
|
|
||||||
ProtocolBridge.getInstance().getProtocolServer().getServer().getConnectionHandlerByID(clientID).sendPacket(new DomainPacket(requestDomain, domain));
|
|
||||||
} else {
|
|
||||||
clientID = objectInputStream.readInt();
|
|
||||||
requestDomain = (RequestDomain) objectInputStream.readObject();
|
|
||||||
domain = (Domain) objectInputStream.readObject();
|
|
||||||
protocolVersion = (ProtocolVersion) objectInputStream.readObject();
|
|
||||||
|
|
||||||
ProtocolBridge.getInstance().getProtocolClient().getClient().getEventManager().executeEvent(new DomainPacketReceivedEvent(protocolVersion, domain, requestDomain, clientID));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final Object clone() throws CloneNotSupportedException {
|
|
||||||
return super.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final boolean equals(Object obj) {
|
|
||||||
return super.equals(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final String toString() {
|
|
||||||
return super.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final int hashCode() {
|
|
||||||
return super.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol.packets.v1_0_0;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolVersion;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
|
|
||||||
public class MessagePacket extends Packet {
|
|
||||||
private ProtocolVersion protocolVersion;
|
|
||||||
private String message;
|
|
||||||
private int clientID;
|
|
||||||
|
|
||||||
public MessagePacket(int id, String message) {
|
|
||||||
super(id);
|
|
||||||
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessagePacket() {
|
|
||||||
super(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
|
|
||||||
protocolVersion = ProtocolBridge.getInstance().getProtocolVersion();
|
|
||||||
|
|
||||||
if (ProtocolBridge.getInstance().isRunningAsServer()) objectOutputStream.writeInt(clientID);
|
|
||||||
else {
|
|
||||||
clientID = ProtocolBridge.getInstance().getProtocolClient().getClient().getClientID();
|
|
||||||
objectOutputStream.writeInt(clientID);
|
|
||||||
}
|
|
||||||
|
|
||||||
objectOutputStream.writeUTF(message);
|
|
||||||
objectOutputStream.writeObject(protocolVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void read(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
|
|
||||||
if (ProtocolBridge.getInstance().isRunningAsServer()) {
|
|
||||||
int clientID = objectInputStream.readInt();
|
|
||||||
String message = objectInputStream.readUTF();
|
|
||||||
protocolVersion = (ProtocolVersion) objectInputStream.readObject();
|
|
||||||
|
|
||||||
ProtocolBridge.getInstance().getProtocolServer().handleMessage(ProtocolBridge.getInstance().getProtocolServer().getServer().getConnectionHandlerByID(clientID), message);
|
|
||||||
} else {
|
|
||||||
int clientID = objectInputStream.readInt();
|
|
||||||
String message = objectInputStream.readUTF();
|
|
||||||
protocolVersion = (ProtocolVersion) objectInputStream.readObject();
|
|
||||||
|
|
||||||
ProtocolBridge.getInstance().getProtocolClient().handleMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final Object clone() throws CloneNotSupportedException {
|
|
||||||
return super.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final boolean equals(Object obj) {
|
|
||||||
return super.equals(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final String toString() {
|
|
||||||
return super.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final int hashCode() {
|
|
||||||
return super.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol.packets.v1_0_0;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolVersion;
|
|
||||||
import org.openautonomousconnection.protocol.domain.Domain;
|
|
||||||
import org.openautonomousconnection.protocol.domain.RequestDomain;
|
|
||||||
import org.openautonomousconnection.protocol.events.v1_0_0.PingPacketReceivedEvent;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
public class PingPacket extends Packet {
|
|
||||||
private RequestDomain requestDomain;
|
|
||||||
private Domain domain;
|
|
||||||
private int clientID;
|
|
||||||
private boolean reachable;
|
|
||||||
private ProtocolVersion protocolVersion;
|
|
||||||
|
|
||||||
public PingPacket(RequestDomain requestDomain, Domain domain, boolean reachable) {
|
|
||||||
this();
|
|
||||||
|
|
||||||
this.requestDomain = requestDomain;
|
|
||||||
this.domain = domain;
|
|
||||||
this.reachable = reachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PingPacket() {
|
|
||||||
super(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
|
|
||||||
protocolVersion = ProtocolBridge.getInstance().getProtocolVersion();
|
|
||||||
|
|
||||||
if (ProtocolBridge.getInstance().isRunningAsServer()) {
|
|
||||||
objectOutputStream.writeInt(clientID);
|
|
||||||
objectOutputStream.writeObject(requestDomain);
|
|
||||||
objectOutputStream.writeObject(domain);
|
|
||||||
objectOutputStream.writeBoolean(reachable);
|
|
||||||
} else {
|
|
||||||
clientID = ProtocolBridge.getInstance().getProtocolClient().getClient().getClientID();
|
|
||||||
objectOutputStream.writeInt(clientID);
|
|
||||||
objectOutputStream.writeObject(requestDomain);
|
|
||||||
}
|
|
||||||
|
|
||||||
objectOutputStream.writeObject(protocolVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void read(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
|
|
||||||
if (ProtocolBridge.getInstance().isRunningAsServer()) {
|
|
||||||
clientID = objectInputStream.readInt();
|
|
||||||
requestDomain = (RequestDomain) objectInputStream.readObject();
|
|
||||||
protocolVersion = (ProtocolVersion) objectInputStream.readObject();
|
|
||||||
|
|
||||||
try {
|
|
||||||
domain = ProtocolBridge.getInstance().getProtocolServer().ping(requestDomain);
|
|
||||||
} catch (SQLException exception) {
|
|
||||||
exception.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
reachable = domain != null;
|
|
||||||
ProtocolBridge.getInstance().getProtocolServer().getServer().getEventManager().executeEvent(new PingPacketReceivedEvent(protocolVersion, domain, requestDomain, reachable, clientID));
|
|
||||||
ProtocolBridge.getInstance().getProtocolServer().getServer().getConnectionHandlerByID(clientID).sendPacket(new PingPacket(requestDomain, domain, reachable));
|
|
||||||
} else {
|
|
||||||
clientID = objectInputStream.readInt();
|
|
||||||
requestDomain = (RequestDomain) objectInputStream.readObject();
|
|
||||||
domain = (Domain) objectInputStream.readObject();
|
|
||||||
boolean reachable = objectInputStream.readBoolean();
|
|
||||||
protocolVersion = (ProtocolVersion) objectInputStream.readObject();
|
|
||||||
|
|
||||||
ProtocolBridge.getInstance().getProtocolClient().getClient().getEventManager().executeEvent(new PingPacketReceivedEvent(protocolVersion, domain, requestDomain, reachable, clientID));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final Object clone() throws CloneNotSupportedException {
|
|
||||||
return super.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final boolean equals(Object obj) {
|
|
||||||
return super.equals(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final String toString() {
|
|
||||||
return super.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final int hashCode() {
|
|
||||||
return super.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectedClient;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
|
||||||
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
|
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolINSServerEvent;
|
||||||
|
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolServerEvent;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.events.S_CustomClientConnectedEvent;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication packet used between client and INS/Web servers.
|
||||||
|
*
|
||||||
|
* <p>Responsibilities:
|
||||||
|
* <ul>
|
||||||
|
* <li>Client → Server: Sends client connection id and protocol version</li>
|
||||||
|
* <li>INS Server → Client: Sends CA key, CA certificate and CA serial files</li>
|
||||||
|
* <li>Performs version compatibility validation</li>
|
||||||
|
* <li>Triggers authentication callbacks on both sides</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class AuthPacket extends OACPacket {
|
||||||
|
|
||||||
|
private ProtocolBridge protocolBridge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new authentication packet for sending CA data or client identity.
|
||||||
|
*
|
||||||
|
* @param protocolBridge The protocol context of the current instance.
|
||||||
|
*/
|
||||||
|
public AuthPacket(ProtocolBridge protocolBridge) {
|
||||||
|
super(8, ProtocolVersion.PV_1_0_0_BETA, ProtocolVersion.PV_1_0_1_BETA);
|
||||||
|
this.protocolBridge = protocolBridge;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWrite(DataOutputStream objectOutputStream) throws IOException {
|
||||||
|
if (protocolBridge.isRunningAsINSServer()) {
|
||||||
|
objectOutputStream.writeBoolean(true);
|
||||||
|
objectOutputStream.writeUTF(protocolBridge.getProtocolValues().protocolVersion.name());
|
||||||
|
|
||||||
|
String caPem = "N/A";
|
||||||
|
|
||||||
|
try {
|
||||||
|
String caPrefix = protocolBridge.getProtocolServer().getFolderStructure().getCaPrefix()
|
||||||
|
+ NetworkUtils.getPublicIPAddress();
|
||||||
|
|
||||||
|
objectOutputStream.writeUTF(caPrefix);
|
||||||
|
|
||||||
|
caPem = FileUtils.readFileFull(new File(
|
||||||
|
protocolBridge.getProtocolServer().getFolderStructure().publicCAFolder,
|
||||||
|
caPrefix + ".pem"));
|
||||||
|
} catch (Exception exception) {
|
||||||
|
protocolBridge.getProtocolValues().logger.exception("Failed to read ca-files", exception);
|
||||||
|
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
objectOutputStream.writeUTF(caPem);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protocolBridge.isRunningAsServer()) {
|
||||||
|
objectOutputStream.writeBoolean(false);
|
||||||
|
objectOutputStream.writeUTF(protocolBridge.getProtocolValues().protocolVersion.name());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protocolBridge.isRunningAsClient()) {
|
||||||
|
UUID clientConnectionId = null;
|
||||||
|
|
||||||
|
if (protocolBridge.getProtocolClient() != null) {
|
||||||
|
if (protocolBridge.getProtocolClient().getClientINSConnection() != null && (protocolBridge.getProtocolClient().getClientServerConnection() == null)) {
|
||||||
|
clientConnectionId = protocolBridge.getProtocolClient().getClientINSConnection().getUniqueID();
|
||||||
|
} else if (protocolBridge.getProtocolClient().getClientServerConnection() != null) {
|
||||||
|
clientConnectionId = protocolBridge.getProtocolClient().getClientServerConnection().getUniqueID();
|
||||||
|
} else if (protocolBridge.getProtocolClient().getClientINSConnection() != null) {
|
||||||
|
clientConnectionId = protocolBridge.getProtocolClient().getClientINSConnection().getUniqueID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
objectOutputStream.writeUTF(clientConnectionId.toString());
|
||||||
|
objectOutputStream.writeUTF(protocolBridge.getProtocolValues().protocolVersion.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRead(DataInputStream objectInputStream, UUID id) throws IOException {
|
||||||
|
if (protocolBridge.isRunningAsServer()) {
|
||||||
|
UUID clientID = UUID.fromString(objectInputStream.readUTF());
|
||||||
|
ProtocolVersion clientVersion = ProtocolVersion.valueOf(objectInputStream.readUTF());
|
||||||
|
|
||||||
|
ConnectedClient connectionHandler = getConnection(protocolBridge.getProtocolServer().getNetwork(), clientID);
|
||||||
|
if (connectionHandler == null) {
|
||||||
|
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!protocolBridge.isVersionSupported(clientVersion)) {
|
||||||
|
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
|
||||||
|
try {
|
||||||
|
connectionHandler.disconnect();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setResponseCode(INSResponseStatus.RESPONSE_AUTH_SUCCESS);
|
||||||
|
|
||||||
|
CustomConnectedClient client = protocolBridge.getProtocolServer().getClientByID(clientID);
|
||||||
|
client.setClientVersion(clientVersion);
|
||||||
|
protocolBridge.getProtocolValues().eventManager.executeEvent(new S_CustomClientConnectedEvent(client));
|
||||||
|
client.getConnection().sendPacket(new AuthPacket(protocolBridge), TransportProtocol.TCP);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protocolBridge.isRunningAsClient()) {
|
||||||
|
boolean fromINS = objectInputStream.readBoolean();
|
||||||
|
ProtocolVersion serverVersion = ProtocolVersion.valueOf(objectInputStream.readUTF());
|
||||||
|
|
||||||
|
if (!fromINS) {
|
||||||
|
protocolBridge.getProtocolClient().setServerVersion(serverVersion);
|
||||||
|
protocolBridge.getProtocolValues().eventManager.executeEvent(
|
||||||
|
new ConnectedToProtocolServerEvent(protocolBridge.getProtocolClient())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (!protocolBridge.isVersionSupported(serverVersion)) {
|
||||||
|
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
|
||||||
|
if (protocolBridge.getProtocolClient() != null && protocolBridge.getProtocolClient().getClientINSConnection() != null) {
|
||||||
|
protocolBridge.getProtocolClient().getClientINSConnection().disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setResponseCode(INSResponseStatus.RESPONSE_AUTH_SUCCESS);
|
||||||
|
|
||||||
|
String caPrefix = objectInputStream.readUTF();
|
||||||
|
String caPem = objectInputStream.readUTF();
|
||||||
|
|
||||||
|
if (caPem.equalsIgnoreCase("N/A")) {
|
||||||
|
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
|
||||||
|
protocolBridge.getProtocolClient().getClientINSConnection().disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] caBytes = caPem.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||||
|
String fp = "N/A";
|
||||||
|
|
||||||
|
try {
|
||||||
|
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256");
|
||||||
|
fp = java.util.HexFormat.of().formatHex(md.digest(caBytes));
|
||||||
|
} catch (NoSuchAlgorithmException ignored) {
|
||||||
|
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
|
||||||
|
protocolBridge.getProtocolClient().getClientINSConnection().disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File caPemFile = new File(protocolBridge.getProtocolClient().getFolderStructure().publicCAFolder, caPrefix + ".pem");
|
||||||
|
|
||||||
|
File fpFile = new File(
|
||||||
|
protocolBridge.getProtocolClient().getFolderStructure().publicCAFolder,
|
||||||
|
caPrefix + ".fp");
|
||||||
|
|
||||||
|
if (!fpFile.exists()) {
|
||||||
|
if (!protocolBridge.getProtocolClient().trustINS(fp)) {
|
||||||
|
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
|
||||||
|
protocolBridge.getProtocolClient().getClientINSConnection().disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String existing = FileUtils.readFileLines(fpFile).getFirst();
|
||||||
|
if (!existing.equalsIgnoreCase(fp)) {
|
||||||
|
if (!protocolBridge.getProtocolClient().trustNewINSFingerprint(existing, fp)) {
|
||||||
|
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
|
||||||
|
protocolBridge.getProtocolClient().getClientINSConnection().disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileUtils.writeFile(fpFile, fp + System.lineSeparator());
|
||||||
|
|
||||||
|
try {
|
||||||
|
FileUtils.writeFile(caPemFile, caPem);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
protocolBridge.getProtocolValues().logger.exception("Failed to create/save ca-files", exception);
|
||||||
|
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
protocolBridge.getProtocolClient().setInsVersion(serverVersion);
|
||||||
|
protocolBridge.getProtocolValues().eventManager.executeEvent(
|
||||||
|
new ConnectedToProtocolINSServerEvent(protocolBridge.getProtocolClient())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectedClient getConnection(NetworkServer server, UUID connectionId) {
|
||||||
|
if (server == null || connectionId == null) return null;
|
||||||
|
|
||||||
|
for (ConnectedClient connection : server.getConnectedClients()) {
|
||||||
|
if (connection != null && connection.getUniqueID().equals(connectionId)) {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Packet used by clients to query INS records from an INS server.
|
||||||
|
* <p>
|
||||||
|
* Contains all information required for resolving an InfoName:
|
||||||
|
* <ul>
|
||||||
|
* <li>TLN</li>
|
||||||
|
* <li>Name</li>
|
||||||
|
* <li>Optional subname </li>
|
||||||
|
* <li>Record type</li>
|
||||||
|
* <li>Client ID for routing responses</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class INSQueryPacket extends OACPacket {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private String TLN;
|
||||||
|
@Getter
|
||||||
|
private String name;
|
||||||
|
@Getter
|
||||||
|
private String sub;
|
||||||
|
@Getter
|
||||||
|
private INSRecordType type;
|
||||||
|
@Getter
|
||||||
|
private UUID clientId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new INS query packet with all required parameters.
|
||||||
|
*
|
||||||
|
* @param tln The top-level namespace.
|
||||||
|
* @param name The InfoName.
|
||||||
|
* @param sub Optional subname ("www") or null.
|
||||||
|
* @param type Record type requested.
|
||||||
|
* @param clientId Sender client ID for routing.
|
||||||
|
*/
|
||||||
|
public INSQueryPacket(String tln, String name, String sub, INSRecordType type, UUID clientId) {
|
||||||
|
this();
|
||||||
|
this.TLN = tln;
|
||||||
|
this.name = name;
|
||||||
|
this.sub = sub;
|
||||||
|
this.type = type;
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registration constructor
|
||||||
|
*/
|
||||||
|
public INSQueryPacket() {
|
||||||
|
super(7, ProtocolVersion.PV_1_0_0_BETA, ProtocolVersion.PV_1_0_1_BETA);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the INS query into the stream.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onWrite(DataOutputStream out) throws IOException {
|
||||||
|
out.writeUTF(TLN);
|
||||||
|
out.writeUTF(name);
|
||||||
|
|
||||||
|
out.writeBoolean(sub != null);
|
||||||
|
if (sub != null) out.writeUTF(sub);
|
||||||
|
|
||||||
|
out.writeUTF(type.name());
|
||||||
|
out.writeUTF(clientId.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserializes the INS query from the stream.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onRead(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
TLN = in.readUTF();
|
||||||
|
name = in.readUTF();
|
||||||
|
|
||||||
|
boolean hasSub = in.readBoolean();
|
||||||
|
sub = hasSub ? in.readUTF() : null;
|
||||||
|
|
||||||
|
type = INSRecordType.valueOf(in.readUTF());
|
||||||
|
clientId = UUID.fromString(in.readUTF());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response packet returned by an INS server after resolving a query.
|
||||||
|
* <p>
|
||||||
|
* Contains:
|
||||||
|
* <ul>
|
||||||
|
* <li>Status code ({@link INSResponseStatus})</li>
|
||||||
|
* <li>List of resolved {@link INSRecord} entries</li>
|
||||||
|
* <li>The ID of the requesting client</li>
|
||||||
|
* </ul>
|
||||||
|
* On the client side, {@link org.openautonomousconnection.protocol.side.client.ProtocolClient#onResponse(INSResponseStatus, List)}
|
||||||
|
* is automatically invoked through {@link #onResponseCodeRead}.
|
||||||
|
*/
|
||||||
|
public final class INSResponsePacket extends OACPacket {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final ProtocolBridge bridge;
|
||||||
|
@Getter
|
||||||
|
private INSResponseStatus status;
|
||||||
|
@Getter
|
||||||
|
private List<INSRecord> records;
|
||||||
|
@Getter
|
||||||
|
private UUID clientId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a populated response packet.
|
||||||
|
*
|
||||||
|
* @param status The resolution status.
|
||||||
|
* @param records List of resolved records.
|
||||||
|
* @param clientId ID of requesting client.
|
||||||
|
* @param bridge Protocol runtime context.
|
||||||
|
*/
|
||||||
|
public INSResponsePacket(INSResponseStatus status, List<INSRecord> records, UUID clientId, ProtocolBridge bridge) {
|
||||||
|
this(bridge);
|
||||||
|
this.status = status;
|
||||||
|
this.records = records;
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registration Constructor
|
||||||
|
*
|
||||||
|
* @param bridge Protocol runtime context.
|
||||||
|
*/
|
||||||
|
public INSResponsePacket(ProtocolBridge bridge) {
|
||||||
|
super(6, ProtocolVersion.PV_1_0_0_BETA, ProtocolVersion.PV_1_0_1_BETA);
|
||||||
|
this.bridge = bridge;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the response status, records and client ID.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onWrite(DataOutputStream out) throws IOException {
|
||||||
|
out.writeUTF(status.name());
|
||||||
|
out.writeInt(records.size());
|
||||||
|
|
||||||
|
for (INSRecord rec : records) {
|
||||||
|
writeObject(out, rec);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.writeUTF(clientId.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserializes the response, reconstructing the record list.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onRead(DataInputStream in, UUID clientID) throws IOException {
|
||||||
|
status = INSResponseStatus.valueOf(in.readUTF());
|
||||||
|
|
||||||
|
int size = in.readInt();
|
||||||
|
records = new ArrayList<>(size);
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
records.add((INSRecord) readObject(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
clientId = UUID.fromString(in.readUTF());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after reading the server's response code.
|
||||||
|
* <p>
|
||||||
|
* If running on a client, forwards the result to the client-side API.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onResponseCodeRead(DataInputStream in, UUID clientID) {
|
||||||
|
if (bridge.isRunningAsClient()) {
|
||||||
|
bridge.getProtocolClient().onResponse(status, records);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,66 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol.side;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
import org.openautonomousconnection.protocol.domain.Domain;
|
|
||||||
import org.openautonomousconnection.protocol.domain.RequestDomain;
|
|
||||||
import org.openautonomousconnection.protocol.listeners.ClientListener;
|
|
||||||
import org.openautonomousconnection.protocol.listeners.ServerListener;
|
|
||||||
import org.openautonomousconnection.protocol.packets.v1_0_0.DomainPacket;
|
|
||||||
import org.openautonomousconnection.protocol.utils.SiteType;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
|
|
||||||
public abstract class ProtocolClient extends DefaultMethodsOverrider {
|
|
||||||
|
|
||||||
private NetworkClient client;
|
|
||||||
private ProtocolBridge protocolBridge;
|
|
||||||
|
|
||||||
public abstract void handleHTMLContent(SiteType siteType, Domain domain, String htmlContent);
|
|
||||||
public abstract void handleMessage(String message);
|
|
||||||
|
|
||||||
public final NetworkClient getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final ProtocolBridge getProtocolBridge() {
|
|
||||||
return protocolBridge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void setProtocolBridge(ProtocolBridge protocolBridge) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
|
|
||||||
this.protocolBridge = protocolBridge;
|
|
||||||
|
|
||||||
client = new NetworkClient.ClientBuilder()
|
|
||||||
.setEventManager(protocolBridge.getProtocolSettings().eventManager).setPacketHandler(protocolBridge.getProtocolSettings().packetHandler)
|
|
||||||
.setPort(protocolBridge.getProtocolSettings().port).setHost(protocolBridge.getProtocolSettings().host).
|
|
||||||
build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void startClient() throws Exception {
|
|
||||||
client.getEventManager().unregisterListener(ServerListener.class);
|
|
||||||
client.getEventManager().registerListener(ClientListener.class);
|
|
||||||
|
|
||||||
client.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void disconnectClient() throws IOException {
|
|
||||||
client.getEventManager().unregisterListener(ClientListener.class);
|
|
||||||
client.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void resolveSite(RequestDomain requestDomain) throws IOException, ClassNotFoundException {
|
|
||||||
if (!client.isConnected()) return;
|
|
||||||
|
|
||||||
client.sendPacket(new DomainPacket(requestDomain, null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol.side;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
|
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
|
||||||
import org.openautonomousconnection.protocol.domain.Domain;
|
|
||||||
import org.openautonomousconnection.protocol.domain.RequestDomain;
|
|
||||||
import org.openautonomousconnection.protocol.listeners.ClientListener;
|
|
||||||
import org.openautonomousconnection.protocol.listeners.ServerListener;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.net.*;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public abstract class ProtocolServer extends DefaultMethodsOverrider {
|
|
||||||
public abstract List<Domain> getDomains() throws SQLException;
|
|
||||||
public abstract List<String> getTopLevelDomains() throws SQLException;
|
|
||||||
public abstract void handleMessage(ConnectionHandler connectionHandler, String message);
|
|
||||||
public abstract String getDNSServerInfoSite() throws SQLException;
|
|
||||||
public abstract String getInfoSite(String topLevelDomain) throws SQLException;
|
|
||||||
public abstract String getInterfaceSite() throws SQLException;
|
|
||||||
|
|
||||||
private NetworkServer server;
|
|
||||||
private ProtocolBridge protocolBridge;
|
|
||||||
|
|
||||||
public final void setProtocolBridge(ProtocolBridge protocolBridge) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
|
|
||||||
this.protocolBridge = protocolBridge;
|
|
||||||
|
|
||||||
server = new NetworkServer.ServerBuilder()
|
|
||||||
.setEventManager(protocolBridge.getProtocolSettings().eventManager).setPacketHandler(protocolBridge.getProtocolSettings().packetHandler)
|
|
||||||
.setPort(protocolBridge.getProtocolSettings().port).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final ProtocolBridge getProtocolBridge() {
|
|
||||||
return protocolBridge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final boolean startServer() throws Exception {
|
|
||||||
server.getEventManager().registerListener(ServerListener.class);
|
|
||||||
server.getEventManager().unregisterListener(ClientListener.class);
|
|
||||||
|
|
||||||
return server.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final NetworkServer getServer() {
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final boolean stopServer() throws IOException {
|
|
||||||
server.getEventManager().unregisterListener(ServerListener.class);
|
|
||||||
return server.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final Domain ping(RequestDomain requestDomain) throws SQLException {
|
|
||||||
Domain domain = getDomain(requestDomain);
|
|
||||||
boolean reachable = false;
|
|
||||||
|
|
||||||
String destination = domain.parsedDestination();
|
|
||||||
|
|
||||||
if (!destination.startsWith("http://") && !destination.startsWith("https://")) destination = "http://" + destination;
|
|
||||||
|
|
||||||
HttpURLConnection connection = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
URL u = new URL(destination);
|
|
||||||
|
|
||||||
connection = (HttpURLConnection) u.openConnection();
|
|
||||||
connection.setRequestMethod("HEAD");
|
|
||||||
|
|
||||||
int code = connection.getResponseCode();
|
|
||||||
|
|
||||||
reachable = code == 200;
|
|
||||||
} catch (IOException exception) {
|
|
||||||
InetAddress address = null;
|
|
||||||
try {
|
|
||||||
InetAddress address1 = InetAddress.getByName(destination);
|
|
||||||
String ip = address1.getHostAddress();
|
|
||||||
address = InetAddress.getByName(ip);
|
|
||||||
reachable = address.isReachable(10000);
|
|
||||||
} catch (IOException exc) {
|
|
||||||
reachable = false;
|
|
||||||
exc.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
reachable = false;
|
|
||||||
exception.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
if (connection != null) connection.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
return domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final boolean domainExists(RequestDomain domain) throws SQLException {
|
|
||||||
return getDomain(domain) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final boolean domainExists(Domain domain) throws SQLException {
|
|
||||||
return domainExists(new RequestDomain(domain.name, domain.topLevelDomain, domain.getPath()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public final Domain getDomain(RequestDomain domain) throws SQLException {
|
|
||||||
return getDomain(domain.name, domain.topLevelDomain, domain.getPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
public final boolean topLevelDomainExists(String topLevelDomain) throws SQLException {
|
|
||||||
return topLevelDomain.equalsIgnoreCase("oac") || getTopLevelDomains().contains(topLevelDomain);
|
|
||||||
}
|
|
||||||
|
|
||||||
public final Domain getDomain(String name, String topLevelDomain, String path) throws SQLException {
|
|
||||||
if (!topLevelDomainExists(topLevelDomain)) return null;
|
|
||||||
|
|
||||||
if (name.equalsIgnoreCase("info") && topLevelDomain.equalsIgnoreCase("oac")) return new Domain(name, topLevelDomain, getDNSServerInfoSite(), path);
|
|
||||||
if (name.equalsIgnoreCase("interface") && topLevelDomain.equalsIgnoreCase("oac")) return new Domain(name, topLevelDomain, getInterfaceSite(), path);
|
|
||||||
|
|
||||||
if (name.equalsIgnoreCase("info")) return new Domain(name, topLevelDomain, getInfoSite(topLevelDomain), path);
|
|
||||||
|
|
||||||
for (Domain domain : getDomains()) if (domain.name.equals(name) && domain.topLevelDomain.equals(topLevelDomain)) return new Domain(domain.name, domain.topLevelDomain, domain.realDestination(), path);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,395 @@
|
|||||||
|
package org.openautonomousconnection.protocol.side.client;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientDisconnectedEvent;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.utils.PemUtils;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
|
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||||
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class defining the client-side protocol operations and interactions with INS and servers.
|
||||||
|
*/
|
||||||
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.CLIENT)
|
||||||
|
public abstract class ProtocolClient extends EventListener {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final ClientCertificateFolderStructure folderStructure;
|
||||||
|
|
||||||
|
private NetworkClient clientToINS;
|
||||||
|
private NetworkClient clientToServer;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private ProtocolBridge protocolBridge;
|
||||||
|
|
||||||
|
private ProtocolVersion insVersion = null;
|
||||||
|
private ProtocolVersion serverVersion = null;
|
||||||
|
|
||||||
|
public ProtocolClient() {
|
||||||
|
folderStructure = new ClientCertificateFolderStructure();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void buildINSConnection() {
|
||||||
|
if (!protocolBridge.isRunningAsClient())
|
||||||
|
throw new IllegalStateException("Not running as client");
|
||||||
|
|
||||||
|
if (clientToINS != null &&
|
||||||
|
(clientToINS.isConnected() || clientToINS.isTCPConnected() || clientToINS.isUDPConnected()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
clientToINS = new NetworkClient.Builder().sslEnabled(false).
|
||||||
|
packetHandler(protocolBridge.getProtocolValues().packetHandler)
|
||||||
|
.eventManager(protocolBridge.getProtocolValues().eventManager)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void buildServerConnection(String keyPassword, boolean ssl) throws Exception {
|
||||||
|
if (!protocolBridge.isRunningAsClient())
|
||||||
|
throw new IllegalStateException("Not running as client");
|
||||||
|
|
||||||
|
if (clientToServer != null &&
|
||||||
|
(clientToServer.isConnected() || clientToServer.isTCPConnected() || clientToServer.isUDPConnected()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ssl) {
|
||||||
|
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
trustStore.load(null, null);
|
||||||
|
|
||||||
|
int caIndex = 0;
|
||||||
|
for (File caPem : FileUtils.listFiles(folderStructure.publicCAFolder, ".pem", ".crt", ".cer")) {
|
||||||
|
if (caPem.getName().endsWith(".srl")) continue;
|
||||||
|
X509Certificate caCert = PemUtils.loadCertificate(caPem);
|
||||||
|
trustStore.setCertificateEntry("root-ca-" + (caIndex++), caCert);
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
keyStore.load(null, null);
|
||||||
|
char[] keyPass = new char[0];
|
||||||
|
if (keyPassword != null) keyPass = keyPassword.toCharArray();
|
||||||
|
|
||||||
|
Map<String, File> keyFiles = new HashMap<>();
|
||||||
|
Map<String, File> certFiles = new HashMap<>();
|
||||||
|
|
||||||
|
for (File f : FileUtils.listFiles(folderStructure.privateClientFolder, ".key", ".pem")) {
|
||||||
|
if (f.getName().endsWith(".srl")) continue;
|
||||||
|
String base = FileUtils.stripExt(f.getName());
|
||||||
|
if (f.getName().endsWith(".key")) keyFiles.put(base, f);
|
||||||
|
if (f.getName().endsWith(".pem")) certFiles.put(base, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
int clientIndex = 0;
|
||||||
|
for (String base : keyFiles.keySet()) {
|
||||||
|
File keyFile = keyFiles.get(base);
|
||||||
|
File certFile = certFiles.get(base);
|
||||||
|
if (certFile == null) {
|
||||||
|
File alt = new File(folderStructure.publicClientFolder, base + ".pem");
|
||||||
|
if (alt.exists()) certFile = alt;
|
||||||
|
}
|
||||||
|
if (certFile == null) continue;
|
||||||
|
|
||||||
|
PrivateKey key = PemUtils.loadPrivateKey(keyFile);
|
||||||
|
X509Certificate cert = PemUtils.loadCertificate(certFile);
|
||||||
|
|
||||||
|
String alias = "client-" + (clientIndex++);
|
||||||
|
keyStore.setKeyEntry(alias, key, keyPass, new X509Certificate[]{cert});
|
||||||
|
}
|
||||||
|
|
||||||
|
clientToServer = new NetworkClient.Builder().sslEnabled(ssl).
|
||||||
|
packetHandler(protocolBridge.getProtocolValues().packetHandler)
|
||||||
|
.eventManager(protocolBridge.getProtocolValues().eventManager)
|
||||||
|
.keyStore(keyStore, keyPass)
|
||||||
|
.trustStore(trustStore)
|
||||||
|
.build();
|
||||||
|
} else {
|
||||||
|
clientToServer = new NetworkClient.Builder().sslEnabled(ssl).
|
||||||
|
packetHandler(protocolBridge.getProtocolValues().packetHandler)
|
||||||
|
.eventManager(protocolBridge.getProtocolValues().eventManager)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final NetworkClient getClientServerConnection() {
|
||||||
|
return clientToServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void attachBridge(ProtocolBridge bridge) throws Exception {
|
||||||
|
if (this.protocolBridge != null)
|
||||||
|
throw new IllegalStateException("ProtocolBridge already attached!");
|
||||||
|
this.protocolBridge = bridge;
|
||||||
|
protocolBridge.getProtocolValues().eventManager.registerListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final NetworkClient getClientINSConnection() {
|
||||||
|
return clientToINS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkFileExists(File folder, String prefix, String extension) throws CertificateException, IOException {
|
||||||
|
if (folder == null) throw new FileNotFoundException("Folder does not exist");
|
||||||
|
|
||||||
|
File[] files = folder.listFiles();
|
||||||
|
|
||||||
|
if (files == null || files.length == 0)
|
||||||
|
throw new FileNotFoundException("Folder " + folder.getAbsolutePath() + " is empty");
|
||||||
|
|
||||||
|
for (File file : files) {
|
||||||
|
if (!file.getName().startsWith(prefix))
|
||||||
|
throw new CertificateException(file.getAbsolutePath() + " is not valid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final ProtocolVersion getServerVersion() {
|
||||||
|
return serverVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : serverVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setServerVersion(ProtocolVersion serverVersion) {
|
||||||
|
if (this.serverVersion == null) this.serverVersion = serverVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final ProtocolVersion getInsVersion() {
|
||||||
|
return insVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : insVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setInsVersion(ProtocolVersion insVersion) {
|
||||||
|
if (this.insVersion == null) this.insVersion = insVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
|
public final void onDisconnect(ClientDisconnectedEvent event) {
|
||||||
|
if (clientToINS == null || !clientToINS.isConnected()) {
|
||||||
|
insVersion = null;
|
||||||
|
clientToINS = null;
|
||||||
|
disconnectFromServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientToServer == null || !clientToServer.isConnected()) {
|
||||||
|
serverVersion = null;
|
||||||
|
clientToServer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isINSStableServer() {
|
||||||
|
return !isINSBetaServer() && !isINSClassicServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportINSServerStable() {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
|
||||||
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE;
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
return isINSStableServer() || yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isINSBetaServer() {
|
||||||
|
return getInsVersion().getProtocolType() == ProtocolVersion.ProtocolType.BETA;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportINSServerBeta() {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : getInsVersion().getCompatibleVersions()) {
|
||||||
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA;
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
return isINSBetaServer() || yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isINSClassicServer() {
|
||||||
|
return getInsVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportINSServerClassic() {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : getInsVersion().getCompatibleVersions()) {
|
||||||
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
return isINSClassicServer() || yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportINSServerPacket(OACPacket packet) {
|
||||||
|
boolean compatible = false;
|
||||||
|
|
||||||
|
for (ProtocolVersion compatibleVersion : packet.getCompatibleVersions()) {
|
||||||
|
if (!compatible) compatible = supportINSServerVersion(compatibleVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
return compatible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportINSServerVersion(ProtocolVersion targetVersion) {
|
||||||
|
return getInsVersion() == targetVersion || getInsVersion().getCompatibleVersions().contains(targetVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportINSServerProtocol(ProtocolVersion.Protocol protocol) {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : getInsVersion().getCompatibleVersions()) {
|
||||||
|
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
return getInsVersion().getSupportedProtocols().contains(protocol) || yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isStableServer() {
|
||||||
|
return !isBetaServer() && !isClassicServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportServerStable() {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
|
||||||
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE;
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
return isStableServer() || yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isBetaServer() {
|
||||||
|
return getServerVersion().getProtocolType() == ProtocolVersion.ProtocolType.BETA;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportServerBeta() {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
|
||||||
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA;
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
return isBetaServer() || yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isClassicServer() {
|
||||||
|
return getServerVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportServerClassic() {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
|
||||||
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
return isClassicServer() || yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportServerPacket(OACPacket packet) {
|
||||||
|
boolean compatible = false;
|
||||||
|
|
||||||
|
for (ProtocolVersion compatibleVersion : packet.getCompatibleVersions()) {
|
||||||
|
if (!compatible) compatible = supportServerVersion(compatibleVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
return compatible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportServerVersion(ProtocolVersion targetVersion) {
|
||||||
|
return getServerVersion() == targetVersion || getServerVersion().getCompatibleVersions().contains(targetVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean supportServerProtocol(ProtocolVersion.Protocol protocol) {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
|
||||||
|
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
return getServerVersion().getSupportedProtocols().contains(protocol) || yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void sendINSQuery(String tln, String name, String sub, INSRecordType type) throws Exception {
|
||||||
|
if (!protocolBridge.isRunningAsClient()) return;
|
||||||
|
|
||||||
|
getClientINSConnection().sendPacket(
|
||||||
|
new INSQueryPacket(tln, name, sub, type, getClientINSConnection().getUniqueID()),
|
||||||
|
TransportProtocol.TCP
|
||||||
|
);
|
||||||
|
onQuerySent(tln, name, sub, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final void disconnectFromServer() {
|
||||||
|
if (clientToServer != null) {
|
||||||
|
if (clientToINS == null || !clientToINS.isConnected())
|
||||||
|
protocolBridge.getProtocolValues().eventManager.unregisterListener(this);
|
||||||
|
clientToServer.disconnect();
|
||||||
|
clientToServer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onResponse(INSResponseStatus status, List<INSRecord> records) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onQuerySent(String tln, String name, String sub, INSRecordType type) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when no stored INS fingerprint exists yet (trust-on-first-use).
|
||||||
|
*
|
||||||
|
* @param caFingerprint received fingerprint of the INS CA certificate
|
||||||
|
* @return {@code true} to allow the connection; {@code false} to reject it
|
||||||
|
*/
|
||||||
|
public abstract boolean trustINS(String caFingerprint);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a stored INS fingerprint does not match the received one.
|
||||||
|
*
|
||||||
|
* @param oldCAFingerprint previously stored fingerprint
|
||||||
|
* @param newCAFingerprint received fingerprint of the INS CA certificate
|
||||||
|
* @return {@code true} to accept the new fingerprint; {@code false} to reject it
|
||||||
|
*/
|
||||||
|
public abstract boolean trustNewINSFingerprint(String oldCAFingerprint, String newCAFingerprint);
|
||||||
|
|
||||||
|
public static final class ClientCertificateFolderStructure {
|
||||||
|
public final File certificatesFolder;
|
||||||
|
|
||||||
|
public final File publicFolder;
|
||||||
|
public final File privateFolder;
|
||||||
|
|
||||||
|
public final File privateCAFolder;
|
||||||
|
public final File privateClientFolder;
|
||||||
|
|
||||||
|
public final File publicCAFolder;
|
||||||
|
public final File publicClientFolder;
|
||||||
|
|
||||||
|
public ClientCertificateFolderStructure() {
|
||||||
|
certificatesFolder = new File("certificates");
|
||||||
|
|
||||||
|
publicFolder = new File(certificatesFolder, "public");
|
||||||
|
privateFolder = new File(certificatesFolder, "private");
|
||||||
|
|
||||||
|
privateCAFolder = new File(privateFolder, "ca");
|
||||||
|
privateClientFolder = new File(privateFolder, "client");
|
||||||
|
|
||||||
|
publicCAFolder = new File(publicFolder, "ca");
|
||||||
|
publicClientFolder = new File(publicFolder, "client");
|
||||||
|
|
||||||
|
if (!certificatesFolder.exists()) certificatesFolder.mkdirs();
|
||||||
|
|
||||||
|
if (!publicFolder.exists()) publicFolder.mkdirs();
|
||||||
|
if (!privateFolder.exists()) privateFolder.mkdirs();
|
||||||
|
|
||||||
|
if (!privateCAFolder.exists()) privateCAFolder.mkdirs();
|
||||||
|
if (!privateClientFolder.exists()) privateClientFolder.mkdirs();
|
||||||
|
|
||||||
|
if (!publicCAFolder.exists()) publicCAFolder.mkdirs();
|
||||||
|
if (!publicClientFolder.exists()) publicClientFolder.mkdirs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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 ConnectedToProtocolINSServerEvent extends Event {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the ProtocolClient object.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final ProtocolClient client;
|
||||||
|
|
||||||
|
public ConnectedToProtocolINSServerEvent(ProtocolClient client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package org.openautonomousconnection.protocol.side.ins;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
|
||||||
|
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.ProtocolCustomServer;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class representing a INS server in the protocol.
|
||||||
|
*/
|
||||||
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
|
||||||
|
public abstract class ProtocolINSServer extends ProtocolCustomServer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the INS information site URL from the configuration.
|
||||||
|
*
|
||||||
|
* @return The INS information site URL.
|
||||||
|
*/
|
||||||
|
private final String insInfoSite;
|
||||||
|
/**
|
||||||
|
* Gets the INS registration site URL from the configuration.
|
||||||
|
*
|
||||||
|
* @return The INS registration site URL.
|
||||||
|
*/
|
||||||
|
private final String insFrontendSite;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a ProtocolINSServer with the specified configuration file.
|
||||||
|
*
|
||||||
|
* @param insInfoSite The INS-InfoSize (IP:PORT)
|
||||||
|
* @param insFrontendSite The INS-InfoSize (IP:PORT)
|
||||||
|
* @throws IOException If an I/O error occurs.
|
||||||
|
* @throws CertificateException If a certificate error occurs.
|
||||||
|
*/
|
||||||
|
public ProtocolINSServer(String insInfoSite, String insFrontendSite) throws Exception {
|
||||||
|
super("ins", "ins");
|
||||||
|
this.insInfoSite = insInfoSite;
|
||||||
|
this.insFrontendSite = insFrontendSite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String getInsInfoSite() {
|
||||||
|
return insInfoSite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String getInsFrontendSite() {
|
||||||
|
return insFrontendSite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start(int tcpPort) throws IOException, InterruptedException, NoSuchAlgorithmException {
|
||||||
|
getNetwork().start(tcpPort, -1);
|
||||||
|
|
||||||
|
String caPrefix = getFolderStructure().getCaPrefix() + NetworkUtils.getPublicIPAddress();
|
||||||
|
File caPemFile = new File(getFolderStructure().publicCAFolder, caPrefix + ".pem");
|
||||||
|
|
||||||
|
byte[] caBytes = FileUtils.readFileFull(caPemFile).getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||||
|
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256");
|
||||||
|
String fp = java.util.HexFormat.of().formatHex(md.digest(caBytes));
|
||||||
|
|
||||||
|
getProtocolBridge().getProtocolValues().logger.info("CA Fingerprint: " + fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a request for an INS record based on TLN, name, subname and record type.
|
||||||
|
* <p>
|
||||||
|
* The implementation should:
|
||||||
|
* <ul>
|
||||||
|
* <li>Locate the corresponding InfoName in storage</li>
|
||||||
|
* <li>Return all matching {@link INSRecord} entries</li>
|
||||||
|
* <li>Handle CNAME recursion, TTL rules, and filtering for the requested type</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param tln The top-level name.
|
||||||
|
* @param name The InfoName.
|
||||||
|
* @param sub The optional subname , may be {@code null}.
|
||||||
|
* @param type The INS record type being requested.
|
||||||
|
* @return A list of resolved INS records. May be empty if no record exists.
|
||||||
|
*/
|
||||||
|
public abstract List<INSRecord> resolve(String tln, String name, String sub, INSRecordType type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback fired whenever the INS server receives a query packet.
|
||||||
|
* <p>This method is optional.</p>
|
||||||
|
*
|
||||||
|
* @param tln The top-level name of the request.
|
||||||
|
* @param name The InfoName being queried.
|
||||||
|
* @param sub An optional subname, or {@code null}.
|
||||||
|
* @param type The record type requested.
|
||||||
|
*/
|
||||||
|
public void onQueryReceived(String tln, String name, String sub, INSRecordType type) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback fired after an INS response was successfully sent to the client.
|
||||||
|
*
|
||||||
|
* @param tln The requested TLN.
|
||||||
|
* @param name The InfoName.
|
||||||
|
* @param sub Optional subname or {@code null}.
|
||||||
|
* @param type The requested record type.
|
||||||
|
* @param results The records returned to the client.
|
||||||
|
*/
|
||||||
|
public void onResponseSent(String tln, String name, String sub, INSRecordType type, List<INSRecord> results) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback fired when an INS response could not be delivered to the client.
|
||||||
|
*
|
||||||
|
* @param tln The requested TLN.
|
||||||
|
* @param name The InfoName.
|
||||||
|
* @param sub Optional subname.
|
||||||
|
* @param type The record type requested.
|
||||||
|
* @param results The records that would have been sent.
|
||||||
|
* @param exception The exception describing the failure.
|
||||||
|
*/
|
||||||
|
public void onResponseSentFailed(String tln, String name, String sub, INSRecordType type, List<INSRecord> results, Exception exception) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the information endpoint for a given Top-Level Name (TLN).
|
||||||
|
*
|
||||||
|
* <p>This method is part of the INS server's internal resolution logic.
|
||||||
|
* Each TLN
|
||||||
|
* need to define a special "info site" which provides metadata, documentation,
|
||||||
|
* or administrative information for that TLN.
|
||||||
|
*
|
||||||
|
* <p>The returned string must always be in the format:
|
||||||
|
* <pre>
|
||||||
|
* host:port
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>This method is used automatically by the INS protocol handler when a client
|
||||||
|
* queries an InfoName of the form:
|
||||||
|
* <pre>
|
||||||
|
* info.<tln>
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>If no TLN-specific info endpoint exists or the TLN does not exist, the implementation can:
|
||||||
|
* <ul>
|
||||||
|
* <li>return <code>null</code> to signal that no info site is registered</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param tln the top-level name for which the info site should be resolved.
|
||||||
|
* Must not be null. Case-insensitive.
|
||||||
|
* @return a string in <code>"host:port"</code> format representing the TLN's info endpoint,
|
||||||
|
* or <code>null</code> if the TLN has no registered info site.
|
||||||
|
*/
|
||||||
|
public abstract String resolveTLNInfoSite(String tln);
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
package org.openautonomousconnection.protocol.side.server;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectedClient;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.client.S_ClientDisconnectedEvent;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.packets.OACPacket;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
|
public class CustomConnectedClient extends EventListener {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final ConnectedClient connection;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final ProtocolCustomServer server;
|
||||||
|
|
||||||
|
private ProtocolVersion clientVersion = null;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private boolean clientVersionLoaded = false;
|
||||||
|
|
||||||
|
public CustomConnectedClient(ConnectedClient connection, ProtocolCustomServer protocolServer) throws Exception {
|
||||||
|
this.connection = connection;
|
||||||
|
this.server = protocolServer;
|
||||||
|
protocolServer.getProtocolBridge().getProtocolValues().eventManager.registerListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void disconnect() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
connection.disconnect();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
clientVersion = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listener(priority = EventPriority.LOWEST)
|
||||||
|
public final void onDisconnect(S_ClientDisconnectedEvent event) {
|
||||||
|
if (event.getClient().getUniqueID().equals(this.connection.getUniqueID())) {
|
||||||
|
server.getProtocolBridge().getProtocolValues().eventManager.unregisterListener(this);
|
||||||
|
clientVersion = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the protocol version of the connected client.
|
||||||
|
*
|
||||||
|
* @return The protocol version of the client, defaults to PV_1_0_0_CLASSIC if not set.
|
||||||
|
*/
|
||||||
|
public ProtocolVersion getClientVersion() {
|
||||||
|
return clientVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : clientVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the protocol version of the connected client.
|
||||||
|
*
|
||||||
|
* @param clientVersion The protocol version to set.
|
||||||
|
*/
|
||||||
|
public void setClientVersion(ProtocolVersion clientVersion) {
|
||||||
|
if (clientVersionLoaded) return;
|
||||||
|
if (this.clientVersion != null) return;
|
||||||
|
|
||||||
|
this.clientVersion = clientVersion;
|
||||||
|
this.clientVersionLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the connected client is a stable client.
|
||||||
|
*
|
||||||
|
* @return True if the client is stable, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isStableClient() {
|
||||||
|
// Check if the server version is stable
|
||||||
|
return !isBetaClient() && !isClassicClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the connected client supports stable protocol versions.
|
||||||
|
*
|
||||||
|
* @return True if the client supports stable versions, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean supportClientStable() {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
|
||||||
|
// Check if compatible version is stable
|
||||||
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE;
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the client version is stable
|
||||||
|
return isStableClient() || yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the connected client is a beta client.
|
||||||
|
*
|
||||||
|
* @return True if the client is beta, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isBetaClient() {
|
||||||
|
// Check if the server version is beta
|
||||||
|
return getClientVersion().getProtocolType() == ProtocolVersion.ProtocolType.BETA;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the connected client supports beta protocol versions.
|
||||||
|
*
|
||||||
|
* @return True if the client supports beta versions, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean supportClientBeta() {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
|
||||||
|
// Check if compatible version is beta
|
||||||
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA;
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the client version is beta
|
||||||
|
return isBetaClient() || yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the connected client is a classic client.
|
||||||
|
*
|
||||||
|
* @return True if the client is classic, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isClassicClient() {
|
||||||
|
return getClientVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the connected client supports classic protocol versions.
|
||||||
|
*
|
||||||
|
* @return True if the client supports classic versions, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean supportClientClassic() {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
|
||||||
|
// Check if compatible version is classic
|
||||||
|
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the client version is classic
|
||||||
|
return isClassicClient() || yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the connected client supports the protocol version of the given packet.
|
||||||
|
*
|
||||||
|
* @param packet The packet to check support for.
|
||||||
|
* @return True if the client supports the packet's protocol version, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean supportClientPacket(OACPacket packet) {
|
||||||
|
boolean compatible = false;
|
||||||
|
|
||||||
|
for (ProtocolVersion compatibleVersion : packet.getCompatibleVersions()) {
|
||||||
|
if (!compatible) compatible = supportClientVersion(compatibleVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
return compatible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the connected client supports the given protocol version.
|
||||||
|
*
|
||||||
|
* @param targetVersion The protocol version to check support for.
|
||||||
|
* @return True if the client supports the target version, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean supportClientVersion(ProtocolVersion targetVersion) {
|
||||||
|
// Check if the client version matches the target version or is compatible
|
||||||
|
return getClientVersion() == targetVersion || getClientVersion().getCompatibleVersions().contains(targetVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the connected client supports the given protocol.
|
||||||
|
*
|
||||||
|
* @param protocol The protocol to check support for.
|
||||||
|
* @return True if the client supports the protocol, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean supportClientProtocol(ProtocolVersion.Protocol protocol) {
|
||||||
|
boolean yes = false;
|
||||||
|
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
|
||||||
|
// Check if compatible version supports the protocol
|
||||||
|
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
|
||||||
|
if (yes) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the client version supports the protocol
|
||||||
|
return getClientVersion().getSupportedProtocols().contains(protocol) || yes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
package org.openautonomousconnection.protocol.side.web;
|
||||||
|
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
|
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateAckPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamChunkPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamEndPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamStartPacket_v1_0_1_B;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.ProtocolWebServer_1_0_0_B;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketFlags;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||||
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebCompatMapper;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protocol Web Server base for Web v1.0.1 (beta) with built-in v1.0.0 compatibility.
|
||||||
|
*
|
||||||
|
* <p>This class keeps the v1.0.0 entry point ({@link #onWebRequest(CustomConnectedClient, WebRequestPacket)})
|
||||||
|
* and adapts it to the v1.0.1 resource pipeline by mapping v1.0.0 requests to a synthetic
|
||||||
|
* {@link WebResourceRequestPacket} and mapping v1.0.1 responses back to {@link WebResponsePacket}.</p>
|
||||||
|
*
|
||||||
|
* <p>Important:
|
||||||
|
* <ul>
|
||||||
|
* <li>This class does NOT perform network packet dispatch automatically. Your server packet-receive listener
|
||||||
|
* must call the appropriate {@code handle*} methods (v1.0.1) or let the v1.0.0 pipeline call {@code onWebRequest}.</li>
|
||||||
|
* <li>Responses/streams must preserve correlation via {@link WebPacketHeader#getRequestId()} for v1.0.1 clients.</li>
|
||||||
|
* <li>v1.0.0 streaming packets cannot carry request correlation. If you stream, you must
|
||||||
|
* send 1.0.0 stream packets to 1.0.0 clients and v1.0.1 stream packets to v1.0.1 clients.</li>
|
||||||
|
* </ul>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
||||||
|
public abstract class ProtocolWebServer extends ProtocolWebServer_1_0_0_B {
|
||||||
|
|
||||||
|
private static final long LEGACY_TAB_ID = 1L;
|
||||||
|
private static final long LEGACY_PAGE_ID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the web server with the given configuration, authentication, and rules files.
|
||||||
|
*
|
||||||
|
* @param authFile The authentication file.
|
||||||
|
* @param rulesFile The rules file.
|
||||||
|
* @param sessionExpire The expiration time of a Session in minutes.
|
||||||
|
* @param uploadSize The max upload size in MB.
|
||||||
|
* @throws Exception If an error occurs during initialization.
|
||||||
|
*/
|
||||||
|
public ProtocolWebServer(File authFile, File rulesFile, int sessionExpire, int uploadSize) throws Exception {
|
||||||
|
super(authFile, rulesFile, sessionExpire, uploadSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listener(priority = EventPriority.HIGHEST)
|
||||||
|
public final void onPacketRead(C_PacketReadEvent event) {
|
||||||
|
Objects.requireNonNull(event, "event");
|
||||||
|
|
||||||
|
Object packet = event.getPacket();
|
||||||
|
if (packet == null) return;
|
||||||
|
|
||||||
|
CustomConnectedClient client = getClientByID(event.getClient().getUniqueID());
|
||||||
|
if (client == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (packet instanceof WebNavigateRequestPacket nav) {
|
||||||
|
WebNavigateAckPacket ack = handleNavigate(client, nav);
|
||||||
|
client.getConnection().sendPacket(ack, TransportProtocol.TCP);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet instanceof WebResourceRequestPacket req) {
|
||||||
|
WebResourceResponsePacket resp = handleResource(client, req);
|
||||||
|
client.getConnection().sendPacket(resp, TransportProtocol.TCP);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet instanceof WebDocumentApplyRequestPacket apply) {
|
||||||
|
WebDocumentApplyResponsePacket resp = handleDocumentApply(client, apply);
|
||||||
|
client.getConnection().sendPacket(resp, TransportProtocol.TCP);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
getProtocolBridge().getProtocolValues().logger.exception("Failed to handle packet", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server-side dispatcher entry point for a navigation request (packet id 01).
|
||||||
|
*
|
||||||
|
* @param client connected client
|
||||||
|
* @param request navigation request
|
||||||
|
* @return navigation ack to send back
|
||||||
|
*/
|
||||||
|
public final WebNavigateAckPacket handleNavigate(CustomConnectedClient client, WebNavigateRequestPacket request) {
|
||||||
|
Objects.requireNonNull(client, "client");
|
||||||
|
Objects.requireNonNull(request, "request");
|
||||||
|
|
||||||
|
WebNavigateAckPacket ack = onNavigateRequest(client, request);
|
||||||
|
if (ack == null) {
|
||||||
|
ack = new WebNavigateAckPacket(
|
||||||
|
mirrorHeader(request.getHeader(), WebPacketFlags.NAVIGATION),
|
||||||
|
false,
|
||||||
|
"onNavigateRequest returned null"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ack;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server-side dispatcher entry point for a resource request (packet id 03).
|
||||||
|
*
|
||||||
|
* <p>Streaming:
|
||||||
|
* <ul>
|
||||||
|
* <li>If you want to stream the body, return a {@link WebResourceResponsePacket} with an empty body,
|
||||||
|
* and then send {@link WebStreamStartPacket_v1_0_1_B}/{@link WebStreamChunkPacket_v1_0_1_B}/{@link WebStreamEndPacket_v1_0_1_B}
|
||||||
|
* using the SAME {@code requestId}.</li>
|
||||||
|
* </ul>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param client connected client
|
||||||
|
* @param request resource request
|
||||||
|
* @return resource response packet to send back
|
||||||
|
*/
|
||||||
|
public final WebResourceResponsePacket handleResource(CustomConnectedClient client, WebResourceRequestPacket request) {
|
||||||
|
Objects.requireNonNull(client, "client");
|
||||||
|
Objects.requireNonNull(request, "request");
|
||||||
|
|
||||||
|
WebResourceResponsePacket resp = onResourceRequest(client, request);
|
||||||
|
if (resp == null) {
|
||||||
|
resp = new WebResourceResponsePacket(
|
||||||
|
mirrorHeader(request.getHeader(), WebPacketFlags.RESOURCE),
|
||||||
|
500,
|
||||||
|
"text/plain; charset=utf-8",
|
||||||
|
java.util.Collections.emptyMap(),
|
||||||
|
"onResourceRequest returned null".getBytes(StandardCharsets.UTF_8),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server-side dispatcher entry point for a document apply request (packet id 20).
|
||||||
|
*
|
||||||
|
* @param client connected client
|
||||||
|
* @param request apply request
|
||||||
|
* @return apply response
|
||||||
|
*/
|
||||||
|
public final WebDocumentApplyResponsePacket handleDocumentApply(CustomConnectedClient client, WebDocumentApplyRequestPacket request) {
|
||||||
|
Objects.requireNonNull(client, "client");
|
||||||
|
Objects.requireNonNull(request, "request");
|
||||||
|
|
||||||
|
WebDocumentApplyResponsePacket resp = onDocumentApplyRequest(client, request);
|
||||||
|
if (resp == null) {
|
||||||
|
resp = new WebDocumentApplyResponsePacket(
|
||||||
|
mirrorHeader(request.getHeader(), 0),
|
||||||
|
false,
|
||||||
|
"onDocumentApplyRequest returned null"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook: called when the server receives a navigation request.
|
||||||
|
*
|
||||||
|
* @param client connected client
|
||||||
|
* @param request navigation request
|
||||||
|
* @return ack packet (must include mirrored requestId/tabId/pageId/frameId)
|
||||||
|
*/
|
||||||
|
protected abstract WebNavigateAckPacket onNavigateRequest(CustomConnectedClient client, WebNavigateRequestPacket request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook: called when the server receives a resource request.
|
||||||
|
*
|
||||||
|
* @param client connected client
|
||||||
|
* @param request resource request
|
||||||
|
* @return response packet (must include mirrored requestId/tabId/pageId/frameId)
|
||||||
|
*/
|
||||||
|
protected abstract WebResourceResponsePacket onResourceRequest(CustomConnectedClient client, WebResourceRequestPacket request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook: called when the server receives a document apply request.
|
||||||
|
*
|
||||||
|
* @param client connected client
|
||||||
|
* @param request apply request
|
||||||
|
* @return apply response packet (must include mirrored requestId/tabId/pageId/frameId)
|
||||||
|
*/
|
||||||
|
protected abstract WebDocumentApplyResponsePacket onDocumentApplyRequest(CustomConnectedClient client, WebDocumentApplyRequestPacket request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* v1.0.0 entry point.
|
||||||
|
*
|
||||||
|
* <p>This method is invoked by the v1.0.0 server pipeline when a 1.0.0 client sends a 1.0.0 request packet.
|
||||||
|
* We adapt it into the v1.0.1 resource pipeline.</p>
|
||||||
|
*
|
||||||
|
* @param client connected client
|
||||||
|
* @param request 1.0.0 request packet
|
||||||
|
* @return 1.0.0 response packet
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
|
||||||
|
@Override
|
||||||
|
public WebResponsePacket onWebRequest(CustomConnectedClient client, WebRequestPacket request) {
|
||||||
|
Objects.requireNonNull(client, "client");
|
||||||
|
Objects.requireNonNull(request, "request");
|
||||||
|
|
||||||
|
// 1.0.0 requests have no correlation fields. Create synthetic ones.
|
||||||
|
long requestId = WebCompatMapper.nextCompatRequestId();
|
||||||
|
long tabId = LEGACY_TAB_ID;
|
||||||
|
long pageId = LEGACY_PAGE_ID;
|
||||||
|
|
||||||
|
WebResourceRequestPacket mapped = WebCompatMapper.toResourceRequest(requestId, tabId, pageId, request, getProtocolBridge());
|
||||||
|
WebResourceResponsePacket resp = onResourceRequest(client, mapped);
|
||||||
|
|
||||||
|
if (resp == null) {
|
||||||
|
return new WebResponsePacket(
|
||||||
|
500,
|
||||||
|
"text/plain; charset=utf-8",
|
||||||
|
java.util.Collections.emptyMap(),
|
||||||
|
"onResourceRequest returned null".getBytes(StandardCharsets.UTF_8)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return WebCompatMapper.to100BResponse(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a response header that mirrors correlation fields from an incoming header.
|
||||||
|
*
|
||||||
|
* @param incoming incoming header
|
||||||
|
* @param extraFlags extra flags to OR into the mirrored header
|
||||||
|
* @return mirrored header
|
||||||
|
*/
|
||||||
|
protected final WebPacketHeader mirrorHeader(WebPacketHeader incoming, int extraFlags) {
|
||||||
|
Objects.requireNonNull(incoming, "incoming");
|
||||||
|
return new WebPacketHeader(
|
||||||
|
incoming.getRequestId(),
|
||||||
|
incoming.getTabId(),
|
||||||
|
incoming.getPageId(),
|
||||||
|
incoming.getFrameId(),
|
||||||
|
incoming.getFlags() | extraFlags,
|
||||||
|
System.currentTimeMillis()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package org.openautonomousconnection.protocol.side.web.managers;
|
||||||
|
|
||||||
|
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages authentication for web clients.
|
||||||
|
* Loads user credentials from a file and verifies login attempts.
|
||||||
|
*/
|
||||||
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
||||||
|
public final class AuthManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of usernames to their SHA-256 hashed passwords
|
||||||
|
*/
|
||||||
|
private static final Map<String, String> users = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the authentication file and populates the users map.
|
||||||
|
* The file should contain lines in the format: username:hashed_password
|
||||||
|
* Lines starting with '#' are treated as comments and ignored.
|
||||||
|
*
|
||||||
|
* @param authFile The authentication file to load.
|
||||||
|
* @throws IOException If an I/O error occurs reading from the file.
|
||||||
|
*/
|
||||||
|
public static void loadAuthFile(File authFile) throws IOException {
|
||||||
|
// Create the file if it doesn't exist
|
||||||
|
if (!authFile.exists()) authFile.createNewFile();
|
||||||
|
for (String line : Files.readAllLines(authFile.toPath(), StandardCharsets.UTF_8)) {
|
||||||
|
// Trim whitespace and ignore comments/empty lines
|
||||||
|
line = line.trim();
|
||||||
|
if (line.isEmpty() || line.startsWith("#")) continue;
|
||||||
|
|
||||||
|
// Split the line into username and hashed password
|
||||||
|
String[] parts = line.split(":", 2);
|
||||||
|
if (parts.length == 2) {
|
||||||
|
users.put(parts[0], parts[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the provided login and password are valid.
|
||||||
|
*
|
||||||
|
* @param login The username to check.
|
||||||
|
* @param password The password to verify.
|
||||||
|
* @return True if the credentials are valid, false otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean checkAuth(String login, String password) {
|
||||||
|
// Retrieve the stored hashed password for the given username
|
||||||
|
String storedHash = users.get(login);
|
||||||
|
if (storedHash == null) return false;
|
||||||
|
|
||||||
|
// Hash the provided password and compare it to the stored hash
|
||||||
|
String hash = sha256(password);
|
||||||
|
return storedHash.equalsIgnoreCase(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the SHA-256 hash of the given input string.
|
||||||
|
*
|
||||||
|
* @param input The input string to hash.
|
||||||
|
* @return The hexadecimal representation of the SHA-256 hash.
|
||||||
|
*/
|
||||||
|
public static String sha256(String input) {
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
|
byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
// Convert the byte array to a hexadecimal string
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : digest) sb.append(String.format("%02x", b));
|
||||||
|
return sb.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
package org.openautonomousconnection.protocol.side.web.managers;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages access rules for web resources.
|
||||||
|
*
|
||||||
|
* <p>Rules are loaded from a JSON file with the structure:
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "allow": ["index.html", "css/*"],
|
||||||
|
* "deny": ["private/*"],
|
||||||
|
* "auth": ["private/*", "admin/*"]
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>Matching is performed against normalized web paths (forward slashes).
|
||||||
|
* Patterns are treated as glob patterns where '*' matches any sequence.</p>
|
||||||
|
*/
|
||||||
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
||||||
|
public final class RuleManager {
|
||||||
|
|
||||||
|
private static List<Pattern> allow = List.of();
|
||||||
|
private static List<Pattern> deny = List.of();
|
||||||
|
private static List<Pattern> auth = List.of();
|
||||||
|
|
||||||
|
private RuleManager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads rules from a JSON file and compiles patterns.
|
||||||
|
*
|
||||||
|
* @param rulesFile JSON rules file
|
||||||
|
* @throws Exception if reading/parsing fails
|
||||||
|
*/
|
||||||
|
public static void loadRules(File rulesFile) throws Exception {
|
||||||
|
String json = Files.readString(rulesFile.toPath(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
Map<String, List<String>> map = new Gson().fromJson(
|
||||||
|
json,
|
||||||
|
new TypeToken<Map<String, List<String>>>() {
|
||||||
|
}.getType()
|
||||||
|
);
|
||||||
|
|
||||||
|
allow = compileList(map.getOrDefault("allow", List.of()));
|
||||||
|
deny = compileList(map.getOrDefault("deny", List.of()));
|
||||||
|
auth = compileList(map.getOrDefault("auth", List.of()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the path is allowed by allow rules.
|
||||||
|
*
|
||||||
|
* <p>Important: If allow list is empty, everything is allowed (default-open).</p>
|
||||||
|
*
|
||||||
|
* @param path web path (e.g. "/index.html" or "index.html")
|
||||||
|
* @return true if allowed
|
||||||
|
*/
|
||||||
|
public static boolean isAllowed(String path) {
|
||||||
|
String p = normalizePath(path);
|
||||||
|
|
||||||
|
// Default-open behavior if allow list is empty
|
||||||
|
if (allow.isEmpty()) return true;
|
||||||
|
|
||||||
|
return matchesAny(p, allow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the path is denied.
|
||||||
|
*
|
||||||
|
* @param path web path
|
||||||
|
* @return true if denied
|
||||||
|
*/
|
||||||
|
public static boolean isDenied(String path) {
|
||||||
|
String p = normalizePath(path);
|
||||||
|
return matchesAny(p, deny);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the path requires authentication.
|
||||||
|
*
|
||||||
|
* @param path web path
|
||||||
|
* @return true if auth required
|
||||||
|
*/
|
||||||
|
public static boolean requiresAuth(String path) {
|
||||||
|
String p = normalizePath(path);
|
||||||
|
return matchesAny(p, auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean matchesAny(String normalizedPath, List<Pattern> patterns) {
|
||||||
|
for (Pattern pat : patterns) {
|
||||||
|
if (pat.matcher(normalizedPath).matches()) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Pattern> compileList(List<String> globs) {
|
||||||
|
if (globs == null || globs.isEmpty()) return List.of();
|
||||||
|
return globs.stream()
|
||||||
|
.map(RuleManager::compileGlob)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles a glob pattern to a full-match regex Pattern.
|
||||||
|
*
|
||||||
|
* <p>Rules:
|
||||||
|
* <ul>
|
||||||
|
* <li>'*' matches any sequence of characters (including '/')</li>
|
||||||
|
* <li>All other regex meta chars are escaped</li>
|
||||||
|
* <li>Matching is performed against the entire normalized path</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param glob glob pattern, e.g. "css/*" or "private/*"
|
||||||
|
* @return compiled Pattern
|
||||||
|
*/
|
||||||
|
private static Pattern compileGlob(String glob) {
|
||||||
|
String g = normalizePath(glob);
|
||||||
|
|
||||||
|
StringBuilder regex = new StringBuilder();
|
||||||
|
regex.append("^");
|
||||||
|
|
||||||
|
for (int i = 0; i < g.length(); i++) {
|
||||||
|
char c = g.charAt(i);
|
||||||
|
if (c == '*') {
|
||||||
|
regex.append(".*");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape regex metacharacters
|
||||||
|
if ("\\.[]{}()+-^$|?".indexOf(c) >= 0) {
|
||||||
|
regex.append("\\");
|
||||||
|
}
|
||||||
|
regex.append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
regex.append("$");
|
||||||
|
return Pattern.compile(regex.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes a web path:
|
||||||
|
* <ul>
|
||||||
|
* <li>null -> ""</li>
|
||||||
|
* <li>backslashes -> forward slashes</li>
|
||||||
|
* <li>leading '/' removed</li>
|
||||||
|
* <li>no URL decoding (must be done at parser level if needed)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param path input path
|
||||||
|
* @return normalized path
|
||||||
|
*/
|
||||||
|
private static String normalizePath(String path) {
|
||||||
|
if (path == null) return "";
|
||||||
|
|
||||||
|
String p = path.trim().replace('\\', '/');
|
||||||
|
|
||||||
|
while (p.startsWith("/")) {
|
||||||
|
p = p.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package org.openautonomousconnection.protocol.side.web.managers;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||||
|
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
|
||||||
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages user sessions for web clients.
|
||||||
|
* Provides methods to create, validate, and invalidate sessions.
|
||||||
|
*/
|
||||||
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
||||||
|
public final class SessionManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of session IDs to Session objects.
|
||||||
|
*/
|
||||||
|
private static final Map<String, Session> sessions = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Secure random number generator for session ID creation.
|
||||||
|
*/
|
||||||
|
private static final SecureRandom secureRandom = new SecureRandom();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new session for the given user.
|
||||||
|
*
|
||||||
|
* @param login The username associated with the session.
|
||||||
|
* @param ip The IP address of the client.
|
||||||
|
* @param userAgent The User-Agent string of the client.
|
||||||
|
* @param protocolWebServer The Protocol WebServer for the unique Session
|
||||||
|
* @return The generated session ID.
|
||||||
|
* @throws IOException If an I/O error occurs.
|
||||||
|
*/
|
||||||
|
public static String create(String login, String ip, String userAgent, ProtocolWebServer protocolWebServer) throws IOException {
|
||||||
|
// Generate a secure random session ID
|
||||||
|
byte[] bytes = new byte[32];
|
||||||
|
secureRandom.nextBytes(bytes);
|
||||||
|
|
||||||
|
// Encode the bytes to a URL-safe Base64 string
|
||||||
|
String sessionId = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes) + protocolWebServer.getUniqueSessionString();
|
||||||
|
|
||||||
|
// Create and store the new session
|
||||||
|
sessions.put(sessionId, new Session(login, ip, userAgent, protocolWebServer));
|
||||||
|
return sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a session ID against the provided IP and User-Agent.
|
||||||
|
*
|
||||||
|
* @param sessionId The session ID to validate.
|
||||||
|
* @param ip The IP address of the client.
|
||||||
|
* @param userAgent The User-Agent string of the client.
|
||||||
|
* @param protocolWebServer The Protocol WebServer to get the config for refreshing
|
||||||
|
* @return True if the session is valid, false otherwise.
|
||||||
|
* @throws IOException If an I/O error occurs.
|
||||||
|
*/
|
||||||
|
public static boolean isValid(String sessionId, String ip, String userAgent, ProtocolWebServer protocolWebServer) throws IOException {
|
||||||
|
// Retrieve the session associated with the session ID
|
||||||
|
Session session = sessions.get(sessionId);
|
||||||
|
|
||||||
|
// Check if the session exists, is not expired, and matches the IP and User-Agent
|
||||||
|
if (session == null || session.isExpired() || !session.matches(ip, userAgent)) {
|
||||||
|
sessions.remove(sessionId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh the session expiration time
|
||||||
|
session.refresh(protocolWebServer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates a session, removing it from the active sessions.
|
||||||
|
*
|
||||||
|
* @param sessionId The session ID to invalidate.
|
||||||
|
*/
|
||||||
|
public static void invalidate(String sessionId) {
|
||||||
|
sessions.remove(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the username associated with a valid session ID.
|
||||||
|
*
|
||||||
|
* @param sessionId The session ID to look up.
|
||||||
|
* @return The username if the session is valid, null otherwise.
|
||||||
|
*/
|
||||||
|
public static String getUser(String sessionId) {
|
||||||
|
// Retrieve the session associated with the session ID
|
||||||
|
Session session = sessions.get(sessionId);
|
||||||
|
|
||||||
|
// Check if the session exists and is not expired
|
||||||
|
if (session == null || session.isExpired()) {
|
||||||
|
sessions.remove(sessionId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the username associated with the session
|
||||||
|
return session.getLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up expired sessions from the session map.
|
||||||
|
* This method should be called periodically to prevent memory leaks.
|
||||||
|
*/
|
||||||
|
public static void cleanupExpiredSessions() {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
sessions.entrySet().removeIf(entry -> entry.getValue().isExpired());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a user session with associated metadata.
|
||||||
|
*/
|
||||||
|
private static class Session {
|
||||||
|
@Getter
|
||||||
|
String login;
|
||||||
|
String ip;
|
||||||
|
String userAgent;
|
||||||
|
long expiresAt;
|
||||||
|
|
||||||
|
Session(String login, String ip, String userAgent, ProtocolWebServer protocolWebServer) throws IOException {
|
||||||
|
this.login = login;
|
||||||
|
this.ip = ip;
|
||||||
|
this.userAgent = userAgent;
|
||||||
|
this.expiresAt = System.currentTimeMillis() + (long) protocolWebServer.getSessionExpire() * 60 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the session has expired.
|
||||||
|
*
|
||||||
|
* @return True if the session is expired, false otherwise.
|
||||||
|
*/
|
||||||
|
boolean isExpired() {
|
||||||
|
return System.currentTimeMillis() > expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the session matches the given IP and User-Agent.
|
||||||
|
*
|
||||||
|
* @param ip The IP address to check.
|
||||||
|
* @param userAgent The User-Agent string to check.
|
||||||
|
* @return True if both match, false otherwise.
|
||||||
|
*/
|
||||||
|
boolean matches(String ip, String userAgent) {
|
||||||
|
return this.ip.equals(ip) && this.userAgent.equals(userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the session's expiration time.
|
||||||
|
*
|
||||||
|
* @param protocolWebServer The Protocol WebServer to get the Config setting
|
||||||
|
* @throws IOException If an I/O error occurs.
|
||||||
|
*/
|
||||||
|
void refresh(ProtocolWebServer protocolWebServer) throws IOException {
|
||||||
|
this.expiresAt = System.currentTimeMillis() + (long) protocolWebServer.getSessionExpire() * 60 * 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol.utils;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
public class APIInformation extends DefaultMethodsOverrider implements Serializable {
|
|
||||||
public final String username;
|
|
||||||
public final String apiApplication;
|
|
||||||
public final String apiKey;
|
|
||||||
|
|
||||||
public APIInformation(String username, String apiApplication, String apiKey) {
|
|
||||||
this.username = username;
|
|
||||||
this.apiApplication = apiApplication;
|
|
||||||
this.apiKey = apiKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol.utils;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
|
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
public class DomainUtils extends DefaultMethodsOverrider {
|
|
||||||
|
|
||||||
public static String getTopLevelDomain(String url) throws MalformedURLException {
|
|
||||||
URL uri = null;
|
|
||||||
String tldString = null;
|
|
||||||
|
|
||||||
if (url.startsWith(SiteType.PUBLIC.name + "://")) url = url.substring((SiteType.PUBLIC.name + "://").length());
|
|
||||||
if (url.startsWith(SiteType.CLIENT.name + "://")) url = url.substring((SiteType.CLIENT.name + "://").length());
|
|
||||||
if (url.startsWith(SiteType.SERVER.name + "://")) url = url.substring((SiteType.SERVER.name + "://").length());
|
|
||||||
if (url.startsWith(SiteType.PROTOCOL.name + "://")) url = url.substring((SiteType.PROTOCOL.name + "://").length());
|
|
||||||
if (url.startsWith(SiteType.LOCAL.name + "://")) url = url.substring((SiteType.LOCAL.name + "://").length());
|
|
||||||
|
|
||||||
if (!url.startsWith("https://") && !url.startsWith("http://")) url = "https://" + url;
|
|
||||||
|
|
||||||
uri = new URL(url);
|
|
||||||
String[] domainNameParts = uri.getHost().split("\\.");
|
|
||||||
tldString = domainNameParts[domainNameParts.length - 1];
|
|
||||||
|
|
||||||
return tldString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getDomainName(String url) throws URISyntaxException, MalformedURLException {
|
|
||||||
if (url.startsWith(SiteType.PUBLIC.name + "://")) url = url.substring((SiteType.PUBLIC.name + "://").length());
|
|
||||||
if (url.startsWith(SiteType.CLIENT.name + "://")) url = url.substring((SiteType.CLIENT.name + "://").length());
|
|
||||||
if (url.startsWith(SiteType.SERVER.name + "://")) url = url.substring((SiteType.SERVER.name + "://").length());
|
|
||||||
if (url.startsWith(SiteType.PROTOCOL.name + "://")) url = url.substring((SiteType.PROTOCOL.name + "://").length());
|
|
||||||
if (url.startsWith(SiteType.LOCAL.name + "://")) url = url.substring((SiteType.LOCAL.name + "://").length());
|
|
||||||
|
|
||||||
if (!url.startsWith("https://") && !url.startsWith("http://")) url = "https://" + url;
|
|
||||||
|
|
||||||
URI uri = new URI(url);
|
|
||||||
String domain = uri.getHost().replace("." + getTopLevelDomain(url), "");
|
|
||||||
return domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static String getPath(String url) {
|
|
||||||
if (!url.startsWith(SiteType.PUBLIC.name + "://") && !url.startsWith(SiteType.CLIENT.name + "://") &&
|
|
||||||
!url.startsWith(SiteType.SERVER.name + "://") && !url.startsWith(SiteType.PROTOCOL.name + "://") &&
|
|
||||||
!url.startsWith(SiteType.LOCAL.name + "://") && !url.startsWith("http") && !url.startsWith("https")) {
|
|
||||||
url = SiteType.PUBLIC.name + "://" + url;
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] split = url.split("/");
|
|
||||||
if (split.length <= 3) return "";
|
|
||||||
|
|
||||||
StringBuilder path = new StringBuilder();
|
|
||||||
|
|
||||||
for (int i = 3; i < split.length; i++) path.append(split[i]).append("/");
|
|
||||||
|
|
||||||
String pathStr = path.toString();
|
|
||||||
if (pathStr.startsWith("/")) pathStr = pathStr.substring("/".length());
|
|
||||||
if (pathStr.endsWith("/")) pathStr = pathStr.substring(0, pathStr.length() - "/".length());
|
|
||||||
|
|
||||||
return pathStr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol.utils;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
public enum SiteType implements Serializable {
|
|
||||||
CLIENT("oac-client"), SERVER("oac-server"),
|
|
||||||
PUBLIC("oac"), PROTOCOL("oac-protocol"), LOCAL("oac-local");
|
|
||||||
;
|
|
||||||
|
|
||||||
public final String name;
|
|
||||||
|
|
||||||
SiteType(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
|
|
||||||
*
|
|
||||||
* You are unauthorized to remove this copyright.
|
|
||||||
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
|
|
||||||
* See LICENSE-File if exists
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openautonomousconnection.protocol.utils;
|
|
||||||
|
|
||||||
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
|
|
||||||
|
|
||||||
public class WebsitesContent extends DefaultMethodsOverrider {
|
|
||||||
|
|
||||||
public static final String DOMAIN_NOT_FOUND = """
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>404 - Domain not found</title>
|
|
||||||
<meta content="UTF-8" name="charset"/>
|
|
||||||
<meta content="Open Autonomous Connection" name="author"/>
|
|
||||||
<meta content="Domain not found" name="description"/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>404 - This domain was not found</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
""";
|
|
||||||
|
|
||||||
public static final String FILE_NOT_FOUND = """
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>404 - File not found</title>
|
|
||||||
<meta content="UTF-8" name="charset"/>
|
|
||||||
<meta content="Open Autonomous Connection" name="author"/>
|
|
||||||
<meta content="File not found" name="description"/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>404 - This file was not found</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
""";
|
|
||||||
|
|
||||||
public static final String DOMAIN_NOT_REACHABLE = """
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>504 - Site not reachable</title>
|
|
||||||
<meta content="UTF-8" name="charset"/>
|
|
||||||
<meta content="Open Autonomous Connection" name="author"/>
|
|
||||||
<meta content="Site not reached" name="description"/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>504 - This site is currently not reachable</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
""";
|
|
||||||
|
|
||||||
public static String ERROR_OCCURRED(String errorDetails) {
|
|
||||||
return """
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>500 - Error occurred</title>
|
|
||||||
<meta content="UTF-8" name="charset"/>
|
|
||||||
<meta content="Open Autonomous Connection" name="author"/>
|
|
||||||
<meta content="Site not reached" name="description"/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>500 - Error occured while resolving domain!</h1>
|
|
||||||
<h4>Details:</h2>
|
|
||||||
<h5>""" + errorDetails + "</h5>" + """
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String ERROR_OCCURRED = ERROR_OCCURRED("No specified details!");
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
1.0
|
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
package org.openautonomousconnection.protocol.versions;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing different protocol versions, their types, sides, and compatibility.
|
||||||
|
*/
|
||||||
|
public enum ProtocolVersion implements Serializable {
|
||||||
|
/**
|
||||||
|
* For classic OAC-Project => <a href="https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/">"classic"-branch</a>
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
|
||||||
|
PV_1_0_0_CLASSIC("1.0.0", ProtocolType.CLASSIC, ProtocolSide.WEB_INS, List.of(Protocol.HTTP)),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version {@link ProtocolVersion#PV_1_0_0_BETA} has many bugs and does not work as expected (occurred by incompleted packets).
|
||||||
|
* Use {@link ProtocolVersion#PV_1_0_1_BETA} or newer.
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
|
||||||
|
PV_1_0_0_BETA("1.0.0", ProtocolType.BETA, ProtocolSide.ALL, List.of(Protocol.OAC)),
|
||||||
|
|
||||||
|
PV_1_0_1_BETA("1.0.1", ProtocolType.BETA, ProtocolSide.ALL, List.of(Protocol.OAC), PV_1_0_0_BETA),
|
||||||
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The version string of the protocol version.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final String version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the protocol version.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final ProtocolType protocolType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The side(s) the protocol version is intended for.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final ProtocolSide protocolSide;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of protocol versions that are compatible with this version.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final List<ProtocolVersion> compatibleVersions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of supported protocols.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final List<Protocol> supportedProtocols;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for ProtocolVersion enum.
|
||||||
|
*
|
||||||
|
* @param version The version string.
|
||||||
|
* @param protocolType The type of the protocol.
|
||||||
|
* @param protocolSide The side(s) the protocol is intended for.
|
||||||
|
* @param supportedProtocols List of supported protocols.
|
||||||
|
* @param compatibleVersions Varargs of compatible protocol versions.
|
||||||
|
*/
|
||||||
|
ProtocolVersion(String version, ProtocolType protocolType, ProtocolSide protocolSide, List<Protocol> supportedProtocols, ProtocolVersion... compatibleVersions) {
|
||||||
|
this.version = version;
|
||||||
|
this.protocolType = protocolType;
|
||||||
|
this.protocolSide = protocolSide;
|
||||||
|
this.compatibleVersions = new ArrayList<>(Arrays.stream(compatibleVersions).toList());
|
||||||
|
if (!this.compatibleVersions.contains(this)) this.compatibleVersions.add(this);
|
||||||
|
this.supportedProtocols = supportedProtocols;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of the protocol version, including its version, type, side, supported protocols, and compatible versions.
|
||||||
|
*
|
||||||
|
* @return a string representation of the protocol version.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public final String toString() {
|
||||||
|
StringBuilder compatible = new StringBuilder("[");
|
||||||
|
StringBuilder supported = new StringBuilder("[");
|
||||||
|
|
||||||
|
for (ProtocolVersion compatibleVersion : compatibleVersions) compatible.append(compatibleVersion.buildName());
|
||||||
|
for (Protocol supportedProtocol : supportedProtocols) supported.append(supportedProtocol.toString());
|
||||||
|
|
||||||
|
compatible.append("]");
|
||||||
|
supported.append("]");
|
||||||
|
|
||||||
|
return "{version=" + version + ";type=" + protocolType.toString() + ";side=" + protocolSide.toString() + ";supportedProtocols=" + supported + ";compatibleVersions=" + compatible + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a name for the protocol version combining its version and type.
|
||||||
|
*
|
||||||
|
* @return a string representing the name of the protocol version.
|
||||||
|
*/
|
||||||
|
public final String buildName() {
|
||||||
|
return version + "-" + protocolType.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing different protocols.
|
||||||
|
*/
|
||||||
|
public enum Protocol implements Serializable {
|
||||||
|
HTTP,
|
||||||
|
HTTPS,
|
||||||
|
OAC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the protocol in uppercase.
|
||||||
|
*
|
||||||
|
* @return the name of the protocol in uppercase.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public final String toString() {
|
||||||
|
return name().toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing different types of protocol versions.
|
||||||
|
*/
|
||||||
|
public enum ProtocolType implements Serializable {
|
||||||
|
/**
|
||||||
|
* Classic Protocol Type, see old OAC-Project: <a href="https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/">*_old</a>
|
||||||
|
*/
|
||||||
|
CLASSIC,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Beta Protocol Type, may be unstable and subject to change.
|
||||||
|
*/
|
||||||
|
BETA,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stable Protocol Type, recommended for production use.
|
||||||
|
*/
|
||||||
|
STABLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the protocol in uppercase.
|
||||||
|
*
|
||||||
|
* @return the name of the protocol in uppercase.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public final String toString() {
|
||||||
|
return name().toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing different sides where the protocol version can be used.
|
||||||
|
*/
|
||||||
|
public enum ProtocolSide implements Serializable {
|
||||||
|
/**
|
||||||
|
* Client Side only
|
||||||
|
*/
|
||||||
|
CLIENT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INS Server Side only
|
||||||
|
*/
|
||||||
|
INS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server Side only
|
||||||
|
*/
|
||||||
|
WEB,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Both INS and Web Server Side
|
||||||
|
*/
|
||||||
|
WEB_INS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Both Client and INS Server Side
|
||||||
|
*/
|
||||||
|
CLIENT_INS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Both Client and Web Server Side
|
||||||
|
*/
|
||||||
|
CLIENT_WEB,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All Sides
|
||||||
|
*/
|
||||||
|
ALL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the protocol in uppercase.
|
||||||
|
*
|
||||||
|
* @return the name of the protocol in uppercase.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public final String toString() {
|
||||||
|
return name().toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a INS record inside the INS system.
|
||||||
|
* <p>
|
||||||
|
* This is the transport format used in responses and packets.
|
||||||
|
* Each record contains:
|
||||||
|
* <ul>
|
||||||
|
* <li>The record type</li>
|
||||||
|
* <li>A value</li>
|
||||||
|
* <li>Optional priority and weight fields</li>
|
||||||
|
* <li>Optional port field (SRV)</li>
|
||||||
|
* <li>A TTL defining how long the record may be cached</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class INSRecord implements Serializable {
|
||||||
|
|
||||||
|
public INSRecordType type;
|
||||||
|
public String value;
|
||||||
|
public int priority;
|
||||||
|
public int weight;
|
||||||
|
public int port;
|
||||||
|
public int ttl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an INS record object.
|
||||||
|
*
|
||||||
|
* @param type The INS record type.
|
||||||
|
* @param value The record’s data (IPv4, IPv6, hostname, text, etc.).
|
||||||
|
* @param priority MX / SRV priority value (ignored for other types).
|
||||||
|
* @param weight SRV weight (ignored for other types).
|
||||||
|
* @param port SRV port (ignored for other types).
|
||||||
|
* @param ttl Time-to-live for caching.
|
||||||
|
*/
|
||||||
|
public INSRecord(INSRecordType type, String value, int priority, int weight, int port, int ttl) {
|
||||||
|
this.type = type;
|
||||||
|
this.value = value;
|
||||||
|
this.priority = priority;
|
||||||
|
this.weight = weight;
|
||||||
|
this.port = port;
|
||||||
|
this.ttl = ttl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return type + ":" + value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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,26 @@
|
|||||||
|
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents INS record types for the INS protocol.
|
||||||
|
* <p>
|
||||||
|
* Supported types:
|
||||||
|
* <ul>
|
||||||
|
* <li>A — IPv4 address</li>
|
||||||
|
* <li>AAAA — IPv6 address</li>
|
||||||
|
* <li>CNAME — Canonical name redirect</li>
|
||||||
|
* <li>TXT — Arbitrary text</li>
|
||||||
|
* <li>MX — Mail exchanger</li>
|
||||||
|
* <li>SRV — Service locator</li>
|
||||||
|
* <li>NS — Nameserver delegation</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public enum INSRecordType {
|
||||||
|
A,
|
||||||
|
AAAA,
|
||||||
|
CNAME,
|
||||||
|
TXT,
|
||||||
|
MX,
|
||||||
|
SRV,
|
||||||
|
NS,
|
||||||
|
INFO,
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the possible response states returned by the INS server.
|
||||||
|
*/
|
||||||
|
public enum INSResponseStatus {
|
||||||
|
/**
|
||||||
|
* Request succeeded and matching records were found.
|
||||||
|
*/
|
||||||
|
OK,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No records exist for the queried TLN/name/sub/type combination.
|
||||||
|
*/
|
||||||
|
NOT_FOUND,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The query was malformed or missing required parameters.
|
||||||
|
*/
|
||||||
|
INVALID_REQUEST,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal server error occurred while resolving the request.
|
||||||
|
*/
|
||||||
|
SERVER_ERROR,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response is not required for the specific request type.
|
||||||
|
*/
|
||||||
|
RESPONSE_NOT_REQUIRED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication failed.
|
||||||
|
*/
|
||||||
|
RESPONSE_AUTH_FAILED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication succeeded.
|
||||||
|
*/
|
||||||
|
RESPONSE_AUTH_SUCCESS
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.openautonomousconnection.protocol.versions.v1_0_0.classic;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing the protocol versions for the Classic protocol.
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = true, since = "1.0.1-BETA.0.1")
|
||||||
|
public enum Classic_ProtocolVersion implements Serializable {
|
||||||
|
PV_1_0_0("1.0.0");
|
||||||
|
public final String version;
|
||||||
|
|
||||||
|
Classic_ProtocolVersion(String version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user