diff --git a/pom.xml b/pom.xml
index 6b16ef3..feee98b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
org.openautonomousconnection
Protocol
- 1.0.0-BETA.1.1
+ 1.0.1-BETA.0.1
Open Autonomous Connection
https://open-autonomous-connection.org/
@@ -84,12 +84,12 @@
dev.unlegitdqrk
unlegitlibrary
- 1.8.1
+ 1.8.2
org.projectlombok
lombok
- 1.18.38
+ 1.18.42
provided
diff --git a/src/main/java/org/openautonomousconnection/protocol/ProtocolBridge.java b/src/main/java/org/openautonomousconnection/protocol/ProtocolBridge.java
index 706296e..9f27636 100644
--- a/src/main/java/org/openautonomousconnection/protocol/ProtocolBridge.java
+++ b/src/main/java/org/openautonomousconnection/protocol/ProtocolBridge.java
@@ -1,10 +1,10 @@
package org.openautonomousconnection.protocol;
+import dev.unlegitdqrk.unlegitlibrary.addon.AddonLoader;
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode;
import dev.unlegitdqrk.unlegitlibrary.utils.Logger;
import lombok.Getter;
-import lombok.Setter;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.listeners.ClientListener;
import org.openautonomousconnection.protocol.listeners.CustomServerListener;
@@ -14,18 +14,28 @@ 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;
-import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamEndPacket;
-import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamStartPacket;
+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.versions.ProtocolVersion;
+import org.openautonomousconnection.protocol.versions.v1_0_0.beta.ProtocolWebServer_1_0_0_B;
import java.io.File;
import java.io.IOException;
-import java.net.Proxy;
+import java.util.function.Supplier;
/**
* The main bridge class for the protocol connection.
@@ -63,12 +73,8 @@ public final class ProtocolBridge {
@Getter
private ProtocolCustomServer protocolServer;
- /**
- * The proxy for client side
- */
@Getter
- @Setter
- private Proxy proxy;
+ private AddonLoader addonLoader;
/**
* Initialize the ProtocolBridge instance for the client side
@@ -76,24 +82,25 @@ public final class ProtocolBridge {
* @param protocolServer The ProtocolCustomServer instance
* @param protocolValues The ProtocolSettings instance
* @param protocolVersion The ProtocolVersion instance
- * @param logFolder The folder to store the log files
+ * @param logger The logger
+ * @param addonLoader The Addon loader to load custom extensions
* @throws Exception if an error occurs while initializing the ProtocolBridge
*/
- public ProtocolBridge(ProtocolCustomServer protocolServer, ProtocolValues protocolValues, ProtocolVersion protocolVersion, File logFolder) throws Exception {
+ public ProtocolBridge(ProtocolCustomServer protocolServer, ProtocolValues protocolValues, ProtocolVersion protocolVersion,
+ Logger logger, AddonLoader addonLoader) throws Exception {
// Assign the parameters to the class fields
this.protocolServer = protocolServer;
this.protocolValues = protocolValues;
this.protocolVersion = protocolVersion;
+ this.logger = logger;
+ this.addonLoader = addonLoader;
- if (protocolServer instanceof ProtocolINSServer) {
+ if (protocolServer instanceof ProtocolINSServer)
protocolServer.attachBridge(this, null, false, ClientAuthMode.NONE);
- } else
+ else
protocolServer.attachBridge(this, protocolValues.keyPass, protocolValues.ssl, protocolValues.authMode);
- // Initialize the logger and protocol version
- initializeLogger(logFolder);
initializeProtocolVersion();
-
downloadLicenses();
// Register the appropriate listeners and packets
@@ -107,23 +114,24 @@ public final class ProtocolBridge {
* @param protocolClient The ProtocolClient instance
* @param protocolValues The ProtocolSettings instance
* @param protocolVersion The ProtocolVersion instance
- * @param logFolder The folder to store the log files
+ * @param logger The logger
+ * @param addonLoader The Addon loader to load custom extensions
* @throws Exception if an error occurs while initializing the ProtocolBridge
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.CLIENT)
- public ProtocolBridge(ProtocolClient protocolClient, ProtocolValues protocolValues, ProtocolVersion protocolVersion, File logFolder) throws Exception {
+ public ProtocolBridge(ProtocolClient protocolClient, ProtocolValues protocolValues, ProtocolVersion protocolVersion,
+ Logger logger, AddonLoader addonLoader) throws Exception {
// Assign the parameters to the class fields
this.protocolClient = protocolClient;
this.protocolValues = protocolValues;
this.protocolVersion = protocolVersion;
+ this.logger = logger;
+ this.addonLoader = addonLoader;
protocolClient.attachBridge(this);
-
+ initializeProtocolVersion();
downloadLicenses();
- // Initialize the logger and protocol version
- initializeLogger(logFolder);
- initializeProtocolVersion();
// Register the appropriate listeners and packets
registerListeners();
@@ -132,8 +140,10 @@ public final class ProtocolBridge {
private void downloadLicenses() throws IOException {
File licensesFolder = new File("licenses");
+
if (!licensesFolder.exists() || !licensesFolder.isDirectory()) {
if (licensesFolder.exists()) licensesFolder.delete();
+
File output = new File("licenses.zip");
output.createNewFile();
FileUtils.downloadFile("https://open-autonomous-connection.org/assets/licenses.zip", output);
@@ -143,27 +153,63 @@ public final class ProtocolBridge {
}
/**
- * Register the appropriate packets based on the current protocol version
+ * Register the appropriate packets
+ *
+ * Overview of all Packets
+ *
+ *
+ * | ID | Packet | ProtocolVersion |
+ *
+ * | 8 | {@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.AuthPacket AuthPacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA} |
+ * | 7 | {@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket INSQueryPacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA} |
+ * | 6 | {@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSResponsePacket INSResponsePacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA} |
+ * | 10 | {@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket WebRequestPacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA} |
+ * | 9 | {@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket WebResponsePacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA} |
+ * | 11 | {@link WebStreamStartPacket_v1_0_0_B v1_0_0.beta.web.stream.WebStreamStartPacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA} |
+ * | 13 | {@link WebStreamChunkPacket_v1_0_0_B v1_0_0.beta.web.stream.WebStreamChunkPacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA} |
+ * | 12 | {@link WebStreamEndPacket_v1_0_0_B v1_0_0.beta.web.stream.WebStreamEndPacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA} |
+ *
+ * | 1 | {@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateRequestPacket WebNavigateRequestPacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA} |
+ * | 2 | {@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateAckPacket WebNavigateAckPacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA} |
+ * | 3 | {@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket WebResourceRequestPacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA} |
+ * | 4 | {@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket WebResourceResponsePacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA} |
+ * | 14 | {@link WebStreamStartPacket_v1_0_1_B v1_0_1.beta.web.impl.stream.WebStreamStartPacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA} |
+ * | 15 | {@link WebStreamChunkPacket_v1_0_1_B v1_0_1.beta.web.impl.stream.WebStreamChunkPacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA} |
+ * | 16 | {@link WebStreamEndPacket_v1_0_1_B v1_0_1.beta.web.impl.stream.WebStreamEndPacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA} |
+ * | 17 | {@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentSnapshotEventPacket WebDocumentSnapshotEventPacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA} |
+ * | 5 | {@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyResponsePacket WebDocumentApplyResponsePacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA} |
+ * | 18 | {@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyRequestPacket WebDocumentApplyRequestPacket} | {@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA} |
+ *
+ *
+ * @see org.openautonomousconnection.protocol.versions.ProtocolVersion
*/
private void registerPackets() {
// 1.0.0-BETA packets
- if (isPacketSupported(new AuthPacket(this)))
- protocolValues.packetHandler.registerPacket(() -> new AuthPacket(this));
- if (isPacketSupported(new INSQueryPacket())) protocolValues.packetHandler.registerPacket(INSQueryPacket::new);
- if (isPacketSupported(new INSResponsePacket(this)))
- protocolValues.packetHandler.registerPacket(() -> new INSResponsePacket(this));
- if (isPacketSupported(new WebRequestPacket()))
- protocolValues.packetHandler.registerPacket(WebRequestPacket::new);
- if (isPacketSupported(new WebResponsePacket()))
- protocolValues.packetHandler.registerPacket(WebResponsePacket::new);
- if (isPacketSupported(new WebStreamChunkPacket()))
- protocolValues.packetHandler.registerPacket(WebStreamChunkPacket::new);
- if (isPacketSupported(new WebStreamStartPacket()))
- protocolValues.packetHandler.registerPacket(WebStreamStartPacket::new);
- if (isPacketSupported(new WebStreamEndPacket()))
- protocolValues.packetHandler.registerPacket(WebStreamEndPacket::new);
+ registerPacket(AuthPacket::new);
+ 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(WebDocumentApplyRequestPacket::new);
+ registerPacket(WebDocumentApplyResponsePacket::new);
+ registerPacket(WebDocumentSnapshotEventPacket::new);
+ registerPacket(WebNavigateRequestPacket::new);
+ registerPacket(WebNavigateAckPacket::new);
+ registerPacket(WebResourceRequestPacket::new);
+ 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
@@ -184,27 +230,6 @@ public final class ProtocolBridge {
}
}
- /**
- * Initialize the logger instance
- *
- * @param logFolder The folder to store the log files
- */
- private void initializeLogger(File logFolder) {
- // Create a temporary logger instance to avoid final field issues
- Logger tmpLogger = null;
-
- try {
- // Initialize temporary logger
- tmpLogger = new Logger(logFolder, false, true);
- } catch (IOException | NoSuchFieldException | IllegalAccessException exception) {
- exception.printStackTrace();
- System.exit(1);
- }
-
- // Assign the temporary logger to the final field
- this.logger = tmpLogger;
- }
-
/**
* Initialize the protocol version
* Validate if the protocol version is valid for the current side
@@ -244,6 +269,7 @@ public final class ProtocolBridge {
*/
public boolean isProtocolSupported(ProtocolVersion.Protocol protocol) {
boolean yes = false;
+
for (ProtocolVersion compatibleVersion : protocolVersion.getCompatibleVersions()) {
// Check if the compatible version supports the target protocol
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
@@ -324,7 +350,7 @@ public final class ProtocolBridge {
* @return true if the current instance is running as a web server, false otherwise
*/
public boolean isRunningAsWebServer() {
- return isRunningAsServer() && protocolServer instanceof ProtocolWebServer;
+ return isRunningAsServer() && protocolServer instanceof ProtocolWebServer_1_0_0_B;
}
/**
diff --git a/src/main/java/org/openautonomousconnection/protocol/listeners/ClientListener.java b/src/main/java/org/openautonomousconnection/protocol/listeners/ClientListener.java
index bea5444..c88e079 100644
--- a/src/main/java/org/openautonomousconnection/protocol/listeners/ClientListener.java
+++ b/src/main/java/org/openautonomousconnection/protocol/listeners/ClientListener.java
@@ -1,6 +1,7 @@
package org.openautonomousconnection.protocol.listeners;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
+import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientConnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
@@ -37,7 +38,7 @@ public final class ClientListener extends EventListener {
*
* @param event The client connected event.
*/
- @Listener
+ @Listener(priority = EventPriority.HIGHEST)
public void onConnect(ClientConnectedEvent event) {
try {
event.getClient().sendPacket(new AuthPacket(client.getProtocolBridge()), TransportProtocol.TCP);
diff --git a/src/main/java/org/openautonomousconnection/protocol/listeners/CustomServerListener.java b/src/main/java/org/openautonomousconnection/protocol/listeners/CustomServerListener.java
index e2f460f..3733be1 100644
--- a/src/main/java/org/openautonomousconnection/protocol/listeners/CustomServerListener.java
+++ b/src/main/java/org/openautonomousconnection/protocol/listeners/CustomServerListener.java
@@ -13,9 +13,9 @@ import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestP
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.side.web.ProtocolWebServer;
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;
@@ -47,7 +47,7 @@ public final class CustomServerListener extends EventListener {
*
* @param event The connection handler connected event.
*/
- @Listener
+ @Listener(priority = EventPriority.HIGHEST)
public void onConnect(S_ClientConnectedEvent event) {
try {
server.getClients().add(new CustomConnectedClient(event.getClient(), server));
@@ -57,13 +57,13 @@ public final class CustomServerListener extends EventListener {
}
}
- @Listener(priority = EventPriority.HIGH)
+ @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) server.getProtocolBridge().getProtocolServer()).
+ ((ProtocolWebServer_1_0_0_B) server.getProtocolBridge().getProtocolServer()).
onWebRequest(server.getClientByID(event.getClient().getUniqueID()), (WebRequestPacket) event.getPacket()),
TransportProtocol.TCP);
} catch (IOException e) {
@@ -88,7 +88,7 @@ public final class CustomServerListener extends EventListener {
*
* @param event The packet event received by the network system.
*/
- @Listener
+ @Listener(priority = EventPriority.HIGHEST)
public void onPacketINS(S_PacketReadEvent event) {
if (!(event.getPacket() instanceof INSQueryPacket q)) return;
if (!server.getProtocolBridge().isRunningAsINSServer()) return;
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/AuthPacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/AuthPacket.java
index 923bfac..387f25f 100644
--- a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/AuthPacket.java
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/AuthPacket.java
@@ -55,8 +55,6 @@ public final class AuthPacket extends OACPacket {
@Override
public void onWrite(DataOutputStream objectOutputStream) throws IOException {
-
-
if (protocolBridge.isRunningAsINSServer()) {
objectOutputStream.writeBoolean(true);
objectOutputStream.writeUTF(protocolBridge.getProtocolVersion().name());
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/INSQueryPacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/INSQueryPacket.java
index 36e8cf3..4cf7d78 100644
--- a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/INSQueryPacket.java
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/INSQueryPacket.java
@@ -45,7 +45,7 @@ public final class INSQueryPacket extends OACPacket {
* @param clientId Sender client ID for routing.
*/
public INSQueryPacket(String tln, String name, String sub, INSRecordType type, UUID clientId) {
- super(7, ProtocolVersion.PV_1_0_0_BETA);
+ this();
this.TLN = tln;
this.name = name;
this.sub = sub;
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/INSResponsePacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/INSResponsePacket.java
index a12ab33..a9edf4d 100644
--- a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/INSResponsePacket.java
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/INSResponsePacket.java
@@ -46,11 +46,10 @@ public final class INSResponsePacket extends OACPacket {
* @param bridge Protocol runtime context.
*/
public INSResponsePacket(INSResponseStatus status, List records, UUID clientId, ProtocolBridge bridge) {
- super(6, ProtocolVersion.PV_1_0_0_BETA);
+ this(bridge);
this.status = status;
this.records = records;
this.clientId = clientId;
- this.bridge = bridge;
}
/**
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/WebRequestPacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/WebRequestPacket.java
index e763e8c..05c06fc 100644
--- a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/WebRequestPacket.java
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/WebRequestPacket.java
@@ -49,7 +49,7 @@ public final class WebRequestPacket extends OACPacket {
* @param body request body (may be null)
*/
public WebRequestPacket(String path, WebRequestMethod method, Map headers, byte[] body) {
- super(10, ProtocolVersion.PV_1_0_0_BETA);
+ this();
this.path = (path != null) ? path : "/";
this.method = (method != null) ? method : WebRequestMethod.GET;
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/WebResponsePacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/WebResponsePacket.java
index 07c7375..451ef08 100644
--- a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/WebResponsePacket.java
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/WebResponsePacket.java
@@ -48,7 +48,7 @@ public final class WebResponsePacket extends OACPacket {
* @param body response body (may be null)
*/
public WebResponsePacket(int statusCode, String contentType, Map headers, byte[] body) {
- super(9, ProtocolVersion.PV_1_0_0_BETA);
+ this();
this.statusCode = statusCode;
this.contentType = (contentType != null) ? contentType : "text/plain";
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamChunkPacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamChunkPacket_v1_0_0_B.java
similarity index 83%
rename from src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamChunkPacket.java
rename to src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamChunkPacket_v1_0_0_B.java
index 25a50b0..7a86300 100644
--- a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamChunkPacket.java
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamChunkPacket_v1_0_0_B.java
@@ -9,19 +9,19 @@ import java.io.DataOutputStream;
import java.io.IOException;
import java.util.UUID;
-public final class WebStreamChunkPacket extends OACPacket {
+public final class WebStreamChunkPacket_v1_0_0_B extends OACPacket {
@Getter
private int seq;
@Getter
private byte[] data;
- public WebStreamChunkPacket() {
+ public WebStreamChunkPacket_v1_0_0_B() {
super(13, ProtocolVersion.PV_1_0_0_BETA);
}
- public WebStreamChunkPacket(int seq, byte[] data) {
- 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];
}
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamEndPacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamEndPacket_v1_0_0_B.java
similarity index 74%
rename from src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamEndPacket.java
rename to src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamEndPacket_v1_0_0_B.java
index f0707f0..f2076c4 100644
--- a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamEndPacket.java
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamEndPacket_v1_0_0_B.java
@@ -9,17 +9,17 @@ import java.io.DataOutputStream;
import java.io.IOException;
import java.util.UUID;
-public final class WebStreamEndPacket extends OACPacket {
+public final class WebStreamEndPacket_v1_0_0_B extends OACPacket {
@Getter
private boolean ok;
- public WebStreamEndPacket() {
- super(12, ProtocolVersion.PV_1_0_0_BETA);
+ public WebStreamEndPacket_v1_0_0_B() {
+ super(13, ProtocolVersion.PV_1_0_0_BETA);
}
- public WebStreamEndPacket(boolean ok) {
- super(12, ProtocolVersion.PV_1_0_0_BETA);
+ public WebStreamEndPacket_v1_0_0_B(boolean ok) {
+ this();
this.ok = ok;
}
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamStartPacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamStartPacket_v1_0_0_B.java
similarity index 88%
rename from src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamStartPacket.java
rename to src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamStartPacket_v1_0_0_B.java
index 021ca98..05ed689 100644
--- a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamStartPacket.java
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_0/beta/web/stream/WebStreamStartPacket_v1_0_0_B.java
@@ -18,7 +18,7 @@ import java.util.UUID;
* 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.
*/
-public final class WebStreamStartPacket extends OACPacket {
+public final class WebStreamStartPacket_v1_0_0_B extends OACPacket {
@Getter
private int statusCode;
@@ -35,7 +35,7 @@ public final class WebStreamStartPacket extends OACPacket {
/**
* Creates an empty start packet (used by PacketHandler factory).
*/
- public WebStreamStartPacket() {
+ public WebStreamStartPacket_v1_0_0_B() {
super(11, ProtocolVersion.PV_1_0_0_BETA);
}
@@ -47,8 +47,8 @@ public final class WebStreamStartPacket extends OACPacket {
* @param headers headers (may be null)
* @param totalLength total length of the stream (may be -1 if unknown)
*/
- public WebStreamStartPacket(int statusCode, String contentType, Map headers, long totalLength) {
- super(11, ProtocolVersion.PV_1_0_0_BETA);
+ public WebStreamStartPacket_v1_0_0_B(int statusCode, String contentType, Map headers, long totalLength) {
+ this();
this.statusCode = statusCode;
this.contentType = (contentType != null) ? contentType : "application/octet-stream";
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/WebPacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/WebPacket.java
new file mode 100644
index 0000000..dde18b6
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/WebPacket.java
@@ -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.
+ *
+ * Ensures the {@link WebPacketHeader} is always serialized first.
+ */
+@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;
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/document/WebDocumentApplyRequestPacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/document/WebDocumentApplyRequestPacket.java
new file mode 100644
index 0000000..dee51d8
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/document/WebDocumentApplyRequestPacket.java
@@ -0,0 +1,42 @@
+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;
+
+/**
+ * Request: replace the document content with full HTML.
+ */
+@Getter
+public final class WebDocumentApplyRequestPacket extends WebPacket {
+ private String fullHtml;
+
+ /**
+ * Registration constructor.
+ */
+ public WebDocumentApplyRequestPacket() {
+ super(18, ProtocolVersion.PV_1_0_1_BETA);
+ }
+
+ public WebDocumentApplyRequestPacket(WebPacketHeader header, String fullHtml) {
+ this();
+ 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();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/document/WebDocumentApplyResponsePacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/document/WebDocumentApplyResponsePacket.java
new file mode 100644
index 0000000..e844500
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/document/WebDocumentApplyResponsePacket.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/document/WebDocumentSnapshotEventPacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/document/WebDocumentSnapshotEventPacket.java
new file mode 100644
index 0000000..9c3167e
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/document/WebDocumentSnapshotEventPacket.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/navigate/WebNavigateAckPacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/navigate/WebNavigateAckPacket.java
new file mode 100644
index 0000000..a0994c9
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/navigate/WebNavigateAckPacket.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/navigate/WebNavigateRequestPacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/navigate/WebNavigateRequestPacket.java
new file mode 100644
index 0000000..26df01f
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/navigate/WebNavigateRequestPacket.java
@@ -0,0 +1,72 @@
+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.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.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;
+
+ /**
+ * Registration constructor.
+ */
+ public WebNavigateRequestPacket() {
+ super(1, ProtocolVersion.PV_1_0_1_BETA);
+ }
+
+ public WebNavigateRequestPacket(
+ WebPacketHeader header,
+ String url,
+ String referrer,
+ WebTransitionType transitionType,
+ long headerProfileId,
+ WebCacheMode cacheMode
+ ) {
+ this();
+ 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());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/resource/WebResourceRequestPacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/resource/WebResourceRequestPacket.java
new file mode 100644
index 0000000..0c8be25
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/resource/WebResourceRequestPacket.java
@@ -0,0 +1,94 @@
+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.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 headers;
+ private byte[] body;
+ private String bodyContentType;
+ private WebInitiatorType initiatorType;
+ private WebCacheMode cacheMode;
+
+ /**
+ * Registration constructor.
+ */
+ public WebResourceRequestPacket() {
+ super(3, ProtocolVersion.PV_1_0_1_BETA);
+ }
+
+ public WebResourceRequestPacket(
+ WebPacketHeader header,
+ String url,
+ String method,
+ Map headers,
+ byte[] body,
+ String bodyContentType,
+ WebInitiatorType initiatorType,
+ WebCacheMode cacheMode
+ ) {
+ this();
+ 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;
+
+ this.initiatorType = WebInitiatorType.valueOf(in.readUTF());
+ 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);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/resource/WebResourceResponsePacket.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/resource/WebResourceResponsePacket.java
new file mode 100644
index 0000000..77b30c2
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/resource/WebResourceResponsePacket.java
@@ -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
+ *
+ * If the response body is streamed, send this packet with an empty body and set STREAM flag in the header.
+ */
+@Getter
+public final class WebResourceResponsePacket extends WebPacket {
+
+ private int statusCode;
+ private String contentType;
+ private Map 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 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);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/stream/WebStreamChunkPacket_v1_0_1_B.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/stream/WebStreamChunkPacket_v1_0_1_B.java
new file mode 100644
index 0000000..0b29cf5
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/stream/WebStreamChunkPacket_v1_0_1_B.java
@@ -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.
+ *
+ * Correlation is done via {@link WebPacketHeader#getRequestId()}.
+ */
+@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);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/stream/WebStreamEndPacket_v1_0_1_B.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/stream/WebStreamEndPacket_v1_0_1_B.java
new file mode 100644
index 0000000..9ed161f
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/stream/WebStreamEndPacket_v1_0_1_B.java
@@ -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.
+ *
+ * Correlation is done via {@link WebPacketHeader#getRequestId()}.
+ */
+@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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/stream/WebStreamStartPacket_v1_0_1_B.java b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/stream/WebStreamStartPacket_v1_0_1_B.java
new file mode 100644
index 0000000..4a76edb
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/packets/v1_0_1/beta/web/impl/stream/WebStreamStartPacket_v1_0_1_B.java
@@ -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.
+ *
+ * Correlation is done via {@link WebPacketHeader#getRequestId()}.
+ */
+@Getter
+public final class WebStreamStartPacket_v1_0_1_B extends WebPacket {
+ private int statusCode;
+ private String contentType;
+ private Map 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 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();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/side/client/ProtocolClient.java b/src/main/java/org/openautonomousconnection/protocol/side/client/ProtocolClient.java
index 92cc185..890f595 100644
--- a/src/main/java/org/openautonomousconnection/protocol/side/client/ProtocolClient.java
+++ b/src/main/java/org/openautonomousconnection/protocol/side/client/ProtocolClient.java
@@ -1,6 +1,7 @@
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;
@@ -174,7 +175,7 @@ public abstract class ProtocolClient extends EventListener {
if (this.insVersion == null) this.insVersion = insVersion;
}
- @Listener
+ @Listener(priority = EventPriority.HIGHEST)
public final void onDisconnect(ClientDisconnectedEvent event) {
if (clientToINS == null || !clientToINS.isConnected()) {
insVersion = null;
@@ -182,7 +183,7 @@ public abstract class ProtocolClient extends EventListener {
disconnectFromServer();
}
- if (clientToServer != null && !clientToServer.isConnected()) {
+ if (clientToServer == null || !clientToServer.isConnected()) {
serverVersion = null;
clientToServer = null;
}
@@ -254,7 +255,7 @@ public abstract class ProtocolClient extends EventListener {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE;
if (yes) break;
}
- return isBetaServer() || yes;
+ return isStableServer() || yes;
}
public final boolean isBetaServer() {
@@ -267,7 +268,7 @@ public abstract class ProtocolClient extends EventListener {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA;
if (yes) break;
}
- return isStableServer() || yes;
+ return isBetaServer() || yes;
}
public final boolean isClassicServer() {
@@ -312,7 +313,8 @@ public abstract class ProtocolClient extends EventListener {
private final void disconnectFromServer() {
if (clientToServer != null) {
- protocolBridge.getProtocolValues().eventManager.unregisterListener(this);
+ if (clientToINS == null || !clientToINS.isConnected())
+ protocolBridge.getProtocolValues().eventManager.unregisterListener(this);
clientToServer.disconnect();
clientToServer = null;
}
diff --git a/src/main/java/org/openautonomousconnection/protocol/side/client/ProtocolWebClient.java b/src/main/java/org/openautonomousconnection/protocol/side/client/ProtocolWebClient.java
new file mode 100644
index 0000000..45b4006
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/side/client/ProtocolWebClient.java
@@ -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.compat.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}.
+ *
+ * Compatibility strategy:
+ *
+ * - In Web v1.0.0 mode, correlation is only possible if there is exactly ONE in-flight request.
+ * - This client therefore enforces single in-flight request and maps responses/streams
+ * onto that single active {@link WebPacketHeader}.
+ *
+ */
+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 tabs = new ConcurrentHashMap<>();
+
+ /**
+ * Single in-flight correlation header.
+ *
+ * v1.0.0 has no correlation fields; without changing the v1.0.0 server this is the only deterministic mapping.
+ */
+ private final AtomicReference 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
+ );
+
+ getClientServerConnection().sendPacket(pkt, TransportProtocol.TCP);
+ onWebRequestSent(pkt);
+ }
+
+ public final long sendResource(
+ WebHeaderFactory.WebTabContext tab,
+ long frameId,
+ String url,
+ String method,
+ Map 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
+ );
+
+ 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);
+ 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.
+ *
+ * Enforces single in-flight 1.0.0 request.
+ *
+ * @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.
+ *
+ * Your v1.0.0 WebServer uses: header 'x-oac-stream' = '1' for the 202 response before streaming.
+ *
+ * @param resp 1.0.0 response
+ * @return true if likely stream handshake
+ */
+ private boolean looksLike100BStreamHandshake(WebResponsePacket resp) {
+ if (resp == null) return false;
+ Map h = resp.getHeaders();
+ if (h == null) return false;
+
+ // Case-insensitive lookup without allocating
+ for (Map.Entry e : h.entrySet()) {
+ if (e.getKey() != null && e.getValue() != null
+ && e.getKey().equalsIgnoreCase("x-oac-stream")
+ && e.getValue().equals("1")) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/side/server/CustomConnectedClient.java b/src/main/java/org/openautonomousconnection/protocol/side/server/CustomConnectedClient.java
index 5b90ca7..385eec4 100644
--- a/src/main/java/org/openautonomousconnection/protocol/side/server/CustomConnectedClient.java
+++ b/src/main/java/org/openautonomousconnection/protocol/side/server/CustomConnectedClient.java
@@ -38,8 +38,8 @@ public class CustomConnectedClient extends EventListener {
clientVersion = null;
}
- @Listener(priority = EventPriority.HIGH)
- public void onDisconnect(S_ClientDisconnectedEvent event) {
+ @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;
diff --git a/src/main/java/org/openautonomousconnection/protocol/side/server/ProtocolCustomServer.java b/src/main/java/org/openautonomousconnection/protocol/side/server/ProtocolCustomServer.java
index 74982a7..5afba15 100644
--- a/src/main/java/org/openautonomousconnection/protocol/side/server/ProtocolCustomServer.java
+++ b/src/main/java/org/openautonomousconnection/protocol/side/server/ProtocolCustomServer.java
@@ -120,7 +120,7 @@ public abstract class ProtocolCustomServer extends EventListener {
trustStore.setCertificateEntry("root-ca-" + (caIndex++), caCert);
}
- // --- Build server key material (private key + certificate chain) ---
+ // Build server key material (private key + certificate chain)
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
@@ -209,8 +209,8 @@ public abstract class ProtocolCustomServer extends EventListener {
}
}
- @Listener
- public void onStop(ServerStoppedEvent event) {
+ @Listener(priority = EventPriority.HIGHEST)
+ public final void onStop(ServerStoppedEvent event) {
if (event.getServer() == network) {
protocolBridge.getProtocolValues().eventManager.unregisterListener(this);
}
@@ -270,7 +270,7 @@ public abstract class ProtocolCustomServer extends EventListener {
}
}
- @Listener(priority = EventPriority.LOW)
+ @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())) {
diff --git a/src/main/java/org/openautonomousconnection/protocol/side/web/ProtocolWebServer.java b/src/main/java/org/openautonomousconnection/protocol/side/web/ProtocolWebServer.java
index 06d8af3..18c634e 100644
--- a/src/main/java/org/openautonomousconnection/protocol/side/web/ProtocolWebServer.java
+++ b/src/main/java/org/openautonomousconnection/protocol/side/web/ProtocolWebServer.java
@@ -1,131 +1,220 @@
package org.openautonomousconnection.protocol.side.web;
-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.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.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 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.compat.WebCompatMapper;
import java.io.File;
-import java.util.Random;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
/**
- * Represents the web server for the protocol.
+ * Protocol Web Server base for Web v1.0.1 (beta) with built-in v1.0.0 compatibility.
+ *
+ * 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}.
+ *
+ * Important:
+ *
+ * - 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}.
+ * - Responses/streams must preserve correlation via {@link WebPacketHeader#getRequestId()} for v1.0.1 clients.
+ * - 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.
+ *
+ *
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
-public abstract class ProtocolWebServer 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;
+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
+ * @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("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;
+ super(authFile, rulesFile, sessionExpire, uploadSize);
}
/**
- * Called when the server receives a WebRequestPacket from the client.
+ * Server-side dispatcher entry point for a navigation request (packet id 01).
*
- * @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.
+ * @param client connected client
+ * @param request navigation request
+ * @return navigation ack to send back
*/
- public abstract WebResponsePacket onWebRequest(CustomConnectedClient client, WebRequestPacket request);
-}
+ 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).
+ *
+ * Streaming:
+ *
+ * - 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}.
+ *
+ *
+ *
+ * @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.
+ *
+ * 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.
+ *
+ * @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);
+ 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()
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/versions/ProtocolVersion.java b/src/main/java/org/openautonomousconnection/protocol/versions/ProtocolVersion.java
index 1e1056b..b948702 100644
--- a/src/main/java/org/openautonomousconnection/protocol/versions/ProtocolVersion.java
+++ b/src/main/java/org/openautonomousconnection/protocol/versions/ProtocolVersion.java
@@ -12,14 +12,19 @@ import java.util.List;
*/
public enum ProtocolVersion implements Serializable {
/**
- * Support for old OAC-Project => *_old
+ * For classic OAC-Project => "classic"-branch
*/
+ @Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
PV_1_0_0_CLASSIC("1.0.0", ProtocolType.CLASSIC, ProtocolSide.WEB_INS, List.of(Protocol.HTTP)),
/**
- * First Beta Version of OAC-Protocol
+ * Version {@link ProtocolVersion#PV_1_0_0_BETA} has many bugs and does not work as expected (occurred by incompleted packets).
+ * Use {@link ProtocolVersion#PV_1_0_1_BETA} or newer.
*/
- PV_1_0_0_BETA("1.0.0", ProtocolType.BETA, ProtocolSide.ALL, List.of(Protocol.OAC), PV_1_0_0_CLASSIC),
+ @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),
;
/**
diff --git a/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/INSRecordTools.java b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/INSRecordTools.java
index 9d18f60..4ea5dc5 100644
--- a/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/INSRecordTools.java
+++ b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/INSRecordTools.java
@@ -149,9 +149,7 @@ public final class INSRecordTools {
return record.ttl >= 0;
}
- // -------------------------------------------------------------------------
// Validation helpers
- // -------------------------------------------------------------------------
/**
* Validates a collection of records by applying {@link #isValidRecord(INSRecord)}
diff --git a/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/ProtocolWebServer_1_0_0_B.java b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/ProtocolWebServer_1_0_0_B.java
new file mode 100644
index 0000000..d3bc391
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/ProtocolWebServer_1_0_0_B.java
@@ -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);
+}
diff --git a/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/WebRequestMethod.java b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/WebRequestMethod.java
index b98f18a..131c5f3 100644
--- a/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/WebRequestMethod.java
+++ b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/WebRequestMethod.java
@@ -1,5 +1,5 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
public enum WebRequestMethod {
- GET, POST
+ GET, POST, EXECUTE, SCRIPT
}
diff --git a/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/classic/Classic_ProtocolVersion.java b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/classic/Classic_ProtocolVersion.java
index 92a4883..7cb8c31 100644
--- a/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/classic/Classic_ProtocolVersion.java
+++ b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/classic/Classic_ProtocolVersion.java
@@ -5,7 +5,7 @@ import java.io.Serializable;
/**
* Enum representing the protocol versions for the Classic protocol.
*/
-@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
+@Deprecated(forRemoval = true, since = "1.0.1-BETA.0.1")
public enum Classic_ProtocolVersion implements Serializable {
PV_1_0_0("1.0.0");
public final String version;
diff --git a/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebCacheMode.java b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebCacheMode.java
new file mode 100644
index 0000000..b6197d3
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebCacheMode.java
@@ -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
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebHeaderFactory.java b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebHeaderFactory.java
new file mode 100644
index 0000000..1e673de
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebHeaderFactory.java
@@ -0,0 +1,250 @@
+package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
+
+import java.security.SecureRandom;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Factory for creating {@link WebPacketHeader} instances with stable tab/page identifiers and
+ * monotonic request identifiers.
+ *
+ * This class is intended to be used by the WebClient/WebServer integration layer to ensure
+ * every packet carries a valid header and consistent correlation metadata.
+ *
+ * Thread-safety:
+ *
+ * - Request id generation is thread-safe.
+ * - Tab/page allocation methods are thread-safe.
+ * - {@link WebTabContext} is thread-safe for reads; writes are synchronized.
+ *
+ *
+ */
+public final class WebHeaderFactory {
+
+ private static final long MIN_ID = 1L;
+
+ private final AtomicLong requestIdSeq;
+ private final AtomicLong tabIdSeq;
+ private final AtomicLong pageIdSeq;
+
+ /**
+ * Creates a factory with randomized id bases to reduce collisions across process restarts.
+ */
+ public WebHeaderFactory() {
+ SecureRandom rnd = new SecureRandom();
+ this.requestIdSeq = new AtomicLong(Math.max(MIN_ID, rnd.nextLong() & Long.MAX_VALUE));
+ this.tabIdSeq = new AtomicLong(Math.max(MIN_ID, rnd.nextLong() & Long.MAX_VALUE));
+ this.pageIdSeq = new AtomicLong(Math.max(MIN_ID, rnd.nextLong() & Long.MAX_VALUE));
+ }
+
+ /**
+ * Creates a new tab context with a fresh tab id and an initial page id.
+ *
+ * @return new tab context
+ */
+ public WebTabContext createTab() {
+ long tabId = nextTabId();
+ long pageId = nextPageId();
+ return new WebTabContext(tabId, pageId);
+ }
+
+ /**
+ * Starts a new navigation by allocating a new page id for the given tab context.
+ *
+ * Call this when navigation is initiated (typed URL, link click, reload, back/forward).
+ *
+ * @param tab tab context
+ * @return the new page id
+ */
+ public long beginNavigation(WebTabContext tab) {
+ Objects.requireNonNull(tab, "tab");
+ long newPageId = nextPageId();
+ tab.setPageId(newPageId);
+ return newPageId;
+ }
+
+ /**
+ * Creates a header for a navigation request.
+ *
+ * @param tab tab context
+ * @param noStore if true, sets {@link WebPacketFlags#NO_STORE}
+ * @return header
+ */
+ public WebPacketHeader navigation(WebTabContext tab, boolean noStore) {
+ Objects.requireNonNull(tab, "tab");
+ int flags = WebPacketFlags.NAVIGATION;
+ if (noStore) flags |= WebPacketFlags.NO_STORE;
+ return header(nextRequestId(), tab.getTabId(), tab.getPageId(), 0L, flags);
+ }
+
+ /**
+ * Creates a header for a resource request.
+ *
+ * @param tab tab context
+ * @param frameId frame id (0 = main)
+ * @param noStore if true, sets {@link WebPacketFlags#NO_STORE}
+ * @return header
+ */
+ public WebPacketHeader resource(WebTabContext tab, long frameId, boolean noStore) {
+ Objects.requireNonNull(tab, "tab");
+ int flags = WebPacketFlags.RESOURCE;
+ if (noStore) flags |= WebPacketFlags.NO_STORE;
+ return header(nextRequestId(), tab.getTabId(), tab.getPageId(), frameId, flags);
+ }
+
+ /**
+ * Creates a header for a resource response correlated to a request id.
+ *
+ * @param requestId request id to correlate
+ * @param tabId tab id
+ * @param pageId page id
+ * @param frameId frame id
+ * @return header
+ */
+ public WebPacketHeader resourceResponse(long requestId, long tabId, long pageId, long frameId) {
+ return header(requestId, tabId, pageId, frameId, WebPacketFlags.RESOURCE);
+ }
+
+ /**
+ * Creates a header for a streamed payload associated with a request id.
+ *
+ * @param requestId correlated request id
+ * @param tabId tab id
+ * @param pageId page id
+ * @param frameId frame id
+ * @return header
+ */
+ public WebPacketHeader stream(long requestId, long tabId, long pageId, long frameId) {
+ return header(requestId, tabId, pageId, frameId, WebPacketFlags.STREAM);
+ }
+
+ /**
+ * Creates an event header (server->client or client->server).
+ *
+ * @param tabId tab id
+ * @param pageId page id
+ * @param frameId frame id
+ * @return header
+ */
+ public WebPacketHeader event(long tabId, long pageId, long frameId) {
+ return header(nextRequestId(), tabId, pageId, frameId, WebPacketFlags.EVENT);
+ }
+
+ /**
+ * Creates an event header correlated to an existing request id.
+ *
+ * @param requestId request id
+ * @param tabId tab id
+ * @param pageId page id
+ * @param frameId frame id
+ * @return header
+ */
+ public WebPacketHeader correlatedEvent(long requestId, long tabId, long pageId, long frameId) {
+ return header(requestId, tabId, pageId, frameId, WebPacketFlags.EVENT);
+ }
+
+ /**
+ * Creates a header for a document snapshot event.
+ *
+ * @param tab tab context
+ * @param frameId frame id
+ * @return header
+ */
+ public WebPacketHeader documentSnapshotEvent(WebTabContext tab, long frameId) {
+ Objects.requireNonNull(tab, "tab");
+ return header(nextRequestId(), tab.getTabId(), tab.getPageId(), frameId, WebPacketFlags.EVENT);
+ }
+
+ /**
+ * Creates a header for a document apply request.
+ *
+ * @param tab tab context
+ * @param frameId frame id
+ * @return header
+ */
+ public WebPacketHeader documentApply(WebTabContext tab, long frameId) {
+ Objects.requireNonNull(tab, "tab");
+ return header(nextRequestId(), tab.getTabId(), tab.getPageId(), frameId, WebPacketFlags.RESOURCE);
+ }
+
+ /**
+ * Checks whether an incoming packet is stale for a given tab context.
+ *
+ * Stale means: the packet refers to an older pageId than the current tab pageId.
+ *
+ * @param tab tab context
+ * @param incoming incoming header
+ * @return true if stale, false otherwise
+ */
+ public boolean isStale(WebTabContext tab, WebPacketHeader incoming) {
+ Objects.requireNonNull(tab, "tab");
+ Objects.requireNonNull(incoming, "incoming");
+ return incoming.getTabId() == tab.getTabId() && incoming.getPageId() != tab.getPageId();
+ }
+
+ private WebPacketHeader header(long requestId, long tabId, long pageId, long frameId, int flags) {
+ long ts = System.currentTimeMillis();
+ return new WebPacketHeader(requestId, tabId, pageId, frameId, flags, ts);
+ }
+
+ private long nextRequestId() {
+ long v = requestIdSeq.incrementAndGet();
+ return (v < MIN_ID) ? MIN_ID : v;
+ }
+
+ private long nextTabId() {
+ long v = tabIdSeq.incrementAndGet();
+ return (v < MIN_ID) ? MIN_ID : v;
+ }
+
+ private long nextPageId() {
+ long v = pageIdSeq.incrementAndGet();
+ return (v < MIN_ID) ? MIN_ID : v;
+ }
+
+ /**
+ * Represents per-tab state used for header creation and stale detection.
+ */
+ public static final class WebTabContext {
+
+ private final long tabId;
+ private volatile long pageId;
+
+ /**
+ * Creates a tab context.
+ *
+ * @param tabId tab id
+ * @param pageId initial page id
+ */
+ public WebTabContext(long tabId, long pageId) {
+ if (tabId < MIN_ID) throw new IllegalArgumentException("tabId must be >= 1");
+ if (pageId < MIN_ID) throw new IllegalArgumentException("pageId must be >= 1");
+ this.tabId = tabId;
+ this.pageId = pageId;
+ }
+
+ /**
+ * @return stable tab id
+ */
+ public long getTabId() {
+ return tabId;
+ }
+
+ /**
+ * @return current page id
+ */
+ public long getPageId() {
+ return pageId;
+ }
+
+ /**
+ * Sets the current page id.
+ *
+ * @param pageId new page id
+ */
+ public synchronized void setPageId(long pageId) {
+ if (pageId < MIN_ID) throw new IllegalArgumentException("pageId must be >= 1");
+ this.pageId = pageId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebInitiatorType.java b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebInitiatorType.java
new file mode 100644
index 0000000..711c640
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebInitiatorType.java
@@ -0,0 +1,16 @@
+package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
+
+/**
+ * Identifies the initiator of a resource request.
+ */
+public enum WebInitiatorType {
+ DOCUMENT,
+ SCRIPT,
+ STYLE,
+ IMAGE,
+ MEDIA,
+ FONT,
+ XHR,
+ FETCH,
+ OTHER
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebPacketFlags.java b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebPacketFlags.java
new file mode 100644
index 0000000..933004d
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebPacketFlags.java
@@ -0,0 +1,46 @@
+package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
+
+/**
+ * Bitset flags used in WebPacketHeader.
+ */
+public final class WebPacketFlags {
+
+ /**
+ * Packet describes a top-level navigation action.
+ */
+ public static final int NAVIGATION = 1 << 0;
+ /**
+ * Packet describes a resource request/response.
+ */
+ public static final int RESOURCE = 1 << 1;
+ /**
+ * Packet is part of a streamed body transfer.
+ */
+ public static final int STREAM = 1 << 2;
+ /**
+ * Packet is devtools-related.
+ */
+ public static final int DEVTOOLS = 1 << 3;
+ /**
+ * Packet is an event (server->client / client->server).
+ */
+ public static final int EVENT = 1 << 4;
+ /**
+ * Disable caching / do not store.
+ */
+ public static final int NO_STORE = 1 << 5;
+
+ private WebPacketFlags() {
+ }
+
+ /**
+ * Checks if a flag is present.
+ *
+ * @param flags bitset
+ * @param flag flag constant
+ * @return true if present
+ */
+ public static boolean has(int flags, int flag) {
+ return (flags & flag) == flag;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebPacketHeader.java b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebPacketHeader.java
new file mode 100644
index 0000000..45b394a
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebPacketHeader.java
@@ -0,0 +1,81 @@
+package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
+
+import lombok.Getter;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * Shared header for all Web v1.0.1-BETA packets.
+ *
+ * This header MUST be written/read first in every web packet.
+ */
+@Getter
+public final class WebPacketHeader {
+
+ private long requestId;
+ private long tabId;
+ private long pageId;
+ private long frameId;
+ private int flags;
+ private long timestampMs;
+
+ /**
+ * Empty constructor for decoding.
+ */
+ public WebPacketHeader() {
+ }
+
+ /**
+ * Creates a new web header.
+ *
+ * @param requestId correlation id (request/response/stream/events)
+ * @param tabId stable tab id
+ * @param pageId navigation instance id
+ * @param frameId frame id (0 = main frame)
+ * @param flags bitset flags
+ * @param timestampMs unix epoch millis (0 allowed)
+ */
+ public WebPacketHeader(long requestId, long tabId, long pageId, long frameId, int flags, long timestampMs) {
+ this.requestId = requestId;
+ this.tabId = tabId;
+ this.pageId = pageId;
+ this.frameId = frameId;
+ this.flags = flags;
+ this.timestampMs = timestampMs;
+ }
+
+ /**
+ * Reads a header from the stream.
+ *
+ * @param in stream
+ * @return decoded header
+ * @throws IOException on IO error
+ */
+ public static WebPacketHeader read(DataInputStream in) throws IOException {
+ WebPacketHeader h = new WebPacketHeader();
+ h.requestId = in.readLong();
+ h.tabId = in.readLong();
+ h.pageId = in.readLong();
+ h.frameId = in.readLong();
+ h.flags = in.readInt();
+ h.timestampMs = in.readLong();
+ return h;
+ }
+
+ /**
+ * Writes the header to the stream.
+ *
+ * @param out stream
+ * @throws IOException on IO error
+ */
+ public void write(DataOutputStream out) throws IOException {
+ out.writeLong(requestId);
+ out.writeLong(tabId);
+ out.writeLong(pageId);
+ out.writeLong(frameId);
+ out.writeInt(flags);
+ out.writeLong(timestampMs);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebTransitionType.java b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebTransitionType.java
new file mode 100644
index 0000000..ce54c14
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/WebTransitionType.java
@@ -0,0 +1,12 @@
+package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
+
+
+/**
+ * Describes how a navigation was triggered.
+ */
+public enum WebTransitionType {
+ LINK,
+ TYPED,
+ RELOAD,
+ BACK_FORWARD
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/compat/WebCompatMapper.java b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/compat/WebCompatMapper.java
new file mode 100644
index 0000000..55b1f20
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_1/beta/compat/WebCompatMapper.java
@@ -0,0 +1,281 @@
+package org.openautonomousconnection.protocol.versions.v1_0_1.beta.compat;
+
+import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
+import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
+import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamChunkPacket_v1_0_0_B;
+import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamEndPacket_v1_0_0_B;
+import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamStartPacket_v1_0_0_B;
+import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket;
+import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
+import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamChunkPacket_v1_0_1_B;
+import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamEndPacket_v1_0_1_B;
+import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamStartPacket_v1_0_1_B;
+import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
+import 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.WebPacketFlags;
+import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Compatibility mapper between Web protocol v1.0.0
+ * and Web protocol v1.0.1 (beta).
+ *
+ * This class provides pure mapping logic and contains no networking code.
+ *
+ * Usage:
+ *
+ * - Server-side: map 1.0.0 {@link WebRequestPacket} to {@link WebResourceRequestPacket}
+ * - Server-side: map {@link WebResourceResponsePacket} back to {@link WebResponsePacket}
+ * - Client-side: map 1.0.0 responses/streams to v1.0.1 packets so v1.0.1 hooks can be reused
+ *
+ *
+ */
+public final class WebCompatMapper {
+
+ /**
+ * Synthetic request id generator for 1.0.0 requests.
+ */
+ private static final AtomicLong COMPAT_REQUEST_ID = new AtomicLong(1L);
+
+ private WebCompatMapper() {
+ }
+
+ /**
+ * Generates a monotonic synthetic request id.
+ *
+ * @return next request id
+ */
+ public static long nextCompatRequestId() {
+ return COMPAT_REQUEST_ID.getAndIncrement();
+ }
+
+ /**
+ * Maps a v1.0.0 {@link WebRequestPacket} to a v1.0.1 {@link WebResourceRequestPacket}.
+ *
+ * Because v1.0.0 has no tab/page/frame/request correlation model,
+ * synthetic identifiers must be provided.
+ *
+ * @param requestId synthetic request id
+ * @param tabId synthetic tab id
+ * @param pageId synthetic page id
+ * @param requestPacket 1.0.0 request
+ * @return mapped v1.0.1 resource request
+ */
+ public static WebResourceRequestPacket toResourceRequest(
+ long requestId,
+ long tabId,
+ long pageId,
+ WebRequestPacket requestPacket
+ ) {
+ Objects.requireNonNull(requestPacket, "requestPacket");
+
+ String path = requestPacket.getPath();
+ String method = mapMethod(requestPacket.getMethod());
+ Map headers = requestPacket.getHeaders() != null ? requestPacket.getHeaders() : Collections.emptyMap();
+ byte[] body = requestPacket.getBody() != null ? requestPacket.getBody() : new byte[0];
+
+ WebPacketHeader header = new WebPacketHeader(
+ requestId,
+ tabId,
+ pageId,
+ 0L,
+ WebPacketFlags.RESOURCE,
+ System.currentTimeMillis()
+ );
+
+ return new WebResourceRequestPacket(
+ header,
+ path,
+ method,
+ headers,
+ body,
+ null,
+ WebInitiatorType.OTHER,
+ WebCacheMode.DEFAULT
+ );
+ }
+
+ /**
+ * Maps a v1.0.1 {@link WebResourceResponsePacket} back to a 1.0.0 {@link WebResponsePacket}.
+ *
+ * Correlation information is lost because v1.0.0 does not support it.
+ *
+ * @param response v1.0.1 resource response
+ * @return 1.0.0 response packet
+ */
+ public static WebResponsePacket to100BResponse(WebResourceResponsePacket response) {
+ Objects.requireNonNull(response, "response");
+
+ return new WebResponsePacket(
+ response.getStatusCode(),
+ response.getContentType(),
+ response.getHeaders(),
+ response.getBody()
+ );
+ }
+
+ /**
+ * Maps v1.0.1 resource response to 1.0.0 response with overridden body.
+ *
+ * Useful when buffering streamed responses for 1.0.0 clients.
+ *
+ * @param response original v1.0.1 response
+ * @param body buffered full body
+ * @return 1.0.0 response
+ */
+ public static WebResponsePacket to100BResponse(WebResourceResponsePacket response, byte[] body) {
+ Objects.requireNonNull(response, "response");
+
+ return new WebResponsePacket(
+ response.getStatusCode(),
+ response.getContentType(),
+ response.getHeaders(),
+ body != null ? body : new byte[0]
+ );
+ }
+
+ /**
+ * Maps a v1.0.0 {@link WebResponsePacket} to a v1.0.1 {@link WebResourceResponsePacket}
+ * using the provided synthetic correlation header.
+ *
+ * @param correlationHeader synthetic correlation header (requestId/tabId/pageId/frameId)
+ * @param responsePacket 1.0.0 response packet
+ * @return v1.0.1 resource response packet
+ */
+ public static WebResourceResponsePacket toV101ResourceResponse(WebPacketHeader correlationHeader, WebResponsePacket responsePacket) {
+ Objects.requireNonNull(correlationHeader, "correlationHeader");
+ Objects.requireNonNull(responsePacket, "responsePacket");
+
+ WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
+
+ return new WebResourceResponsePacket(
+ header,
+ responsePacket.getStatusCode(),
+ responsePacket.getContentType(),
+ responsePacket.getHeaders(),
+ responsePacket.getBody(),
+ null
+ );
+ }
+
+ /**
+ * Maps a v1.0.0 {@link WebStreamStartPacket_v1_0_0_B} to a v1.0.1 {@link WebStreamStartPacket_v1_0_1_B}
+ * using the provided synthetic correlation header.
+ *
+ * @param correlationHeader synthetic correlation header
+ * @param streamPacket 1.0.0 stream start
+ * @return v1.0.1 stream start
+ */
+ public static WebStreamStartPacket_v1_0_1_B toV101StreamStart(WebPacketHeader correlationHeader, WebStreamStartPacket_v1_0_0_B streamPacket) {
+ Objects.requireNonNull(correlationHeader, "correlationHeader");
+ Objects.requireNonNull(streamPacket, "streamPacket");
+
+ WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
+
+ return new WebStreamStartPacket_v1_0_1_B(
+ header,
+ streamPacket.getStatusCode(),
+ streamPacket.getContentType(),
+ streamPacket.getHeaders(),
+ streamPacket.getTotalLength()
+ );
+ }
+
+ /**
+ * Maps a v1.0.0 {@link WebStreamChunkPacket_v1_0_0_B} to a v1.0.1 {@link WebStreamChunkPacket_v1_0_1_B}
+ * using the provided synthetic correlation header.
+ *
+ * @param correlationHeader synthetic correlation header
+ * @param streamPacket 1.0.0 stream chunk
+ * @return v1.0.1 stream chunk
+ */
+ public static WebStreamChunkPacket_v1_0_1_B toV101StreamChunk(WebPacketHeader correlationHeader, WebStreamChunkPacket_v1_0_0_B streamPacket) {
+ Objects.requireNonNull(correlationHeader, "correlationHeader");
+ Objects.requireNonNull(streamPacket, "streamPacket");
+
+ WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
+
+ return new WebStreamChunkPacket_v1_0_1_B(
+ header,
+ streamPacket.getSeq(),
+ streamPacket.getData()
+ );
+ }
+
+ /**
+ * Maps a v1.0.0 {@link WebStreamEndPacket_v1_0_0_B} to a v1.0.1 {@link WebStreamEndPacket_v1_0_1_B}
+ * using the provided synthetic correlation header.
+ *
+ * @param correlationHeader synthetic correlation header
+ * @param streamPacket v1.0.0 stream end packet
+ * @return v1.0.1 stream end packet
+ */
+ public static WebStreamEndPacket_v1_0_1_B toV101StreamEnd(
+ WebPacketHeader correlationHeader,
+ WebStreamEndPacket_v1_0_0_B streamPacket
+ ) {
+ Objects.requireNonNull(correlationHeader, "correlationHeader");
+ Objects.requireNonNull(streamPacket, "streamPacket");
+
+ WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
+
+ // v1.0.0 has no error string -> null
+ return new WebStreamEndPacket_v1_0_1_B(
+ header,
+ streamPacket.isOk(),
+ null
+ );
+ }
+
+ /**
+ * Creates a header that mirrors correlation fields from the provided header and updates timestamp/flags.
+ *
+ * @param correlationHeader correlation header to mirror
+ * @param extraFlags flags to OR
+ * @return mirrored header
+ */
+ public static WebPacketHeader mirrorCorrelation(WebPacketHeader correlationHeader, int extraFlags) {
+ Objects.requireNonNull(correlationHeader, "correlationHeader");
+ return new WebPacketHeader(
+ correlationHeader.getRequestId(),
+ correlationHeader.getTabId(),
+ correlationHeader.getPageId(),
+ correlationHeader.getFrameId(),
+ correlationHeader.getFlags() | extraFlags,
+ System.currentTimeMillis()
+ );
+ }
+
+ /**
+ * Maps 1.0.0 {@link WebRequestMethod}.
+ *
+ * @param method 1.0.0 method
+ * @return method string
+ */
+ public static String mapMethod(WebRequestMethod method) {
+ if (method == null) return "GET";
+ return switch (method) {
+ case GET -> "GET";
+ case POST, EXECUTE, SCRIPT -> "POST";
+ };
+ }
+
+ /**
+ * Maps 1.0.1 to 1.0.0 {@link WebRequestMethod}.
+ *
+ * @param method 1.0.0 method
+ * @return method string
+ */
+ public static WebRequestMethod map100BMethod(String method) {
+ if (method == null) return WebRequestMethod.GET;
+ return switch (method.toUpperCase()) {
+ case "POST" -> WebRequestMethod.POST;
+ default -> WebRequestMethod.GET;
+ };
+ }
+}
\ No newline at end of file