Introduce 1.0.1-BETA packet model with 1.0.0-BETA backwards compatibility

This commit is contained in:
UnlegitDqrk
2026-02-21 23:12:43 +01:00
parent 0841992363
commit a7ce9982c9
40 changed files with 2228 additions and 213 deletions

View File

@@ -6,7 +6,7 @@
<groupId>org.openautonomousconnection</groupId> <groupId>org.openautonomousconnection</groupId>
<artifactId>Protocol</artifactId> <artifactId>Protocol</artifactId>
<version>1.0.0-BETA.1.1</version> <version>1.0.1-BETA.0.1</version>
<organization> <organization>
<name>Open Autonomous Connection</name> <name>Open Autonomous Connection</name>
<url>https://open-autonomous-connection.org/</url> <url>https://open-autonomous-connection.org/</url>
@@ -84,12 +84,12 @@
<dependency> <dependency>
<groupId>dev.unlegitdqrk</groupId> <groupId>dev.unlegitdqrk</groupId>
<artifactId>unlegitlibrary</artifactId> <artifactId>unlegitlibrary</artifactId>
<version>1.8.1</version> <version>1.8.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.38</version> <version>1.18.42</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>

View File

@@ -1,10 +1,10 @@
package org.openautonomousconnection.protocol; package org.openautonomousconnection.protocol;
import dev.unlegitdqrk.unlegitlibrary.addon.AddonLoader;
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils; import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode; import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode;
import dev.unlegitdqrk.unlegitlibrary.utils.Logger; import dev.unlegitdqrk.unlegitlibrary.utils.Logger;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo; import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.listeners.ClientListener; import org.openautonomousconnection.protocol.listeners.ClientListener;
import org.openautonomousconnection.protocol.listeners.CustomServerListener; 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.INSResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket; 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.WebResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamChunkPacket; 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; 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; import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamStartPacket_v1_0_0_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentSnapshotEventPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateAckPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamChunkPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamEndPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamStartPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.side.client.ProtocolClient; import org.openautonomousconnection.protocol.side.client.ProtocolClient;
import org.openautonomousconnection.protocol.side.ins.ProtocolINSServer; import org.openautonomousconnection.protocol.side.ins.ProtocolINSServer;
import org.openautonomousconnection.protocol.side.server.ProtocolCustomServer; import org.openautonomousconnection.protocol.side.server.ProtocolCustomServer;
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
import org.openautonomousconnection.protocol.versions.ProtocolVersion; import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.ProtocolWebServer_1_0_0_B;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.Proxy; import java.util.function.Supplier;
/** /**
* The main bridge class for the protocol connection. * The main bridge class for the protocol connection.
@@ -63,12 +73,8 @@ public final class ProtocolBridge {
@Getter @Getter
private ProtocolCustomServer protocolServer; private ProtocolCustomServer protocolServer;
/**
* The proxy for client side
*/
@Getter @Getter
@Setter private AddonLoader addonLoader;
private Proxy proxy;
/** /**
* Initialize the ProtocolBridge instance for the client side * Initialize the ProtocolBridge instance for the client side
@@ -76,24 +82,25 @@ public final class ProtocolBridge {
* @param protocolServer The ProtocolCustomServer instance * @param protocolServer The ProtocolCustomServer instance
* @param protocolValues The ProtocolSettings instance * @param protocolValues The ProtocolSettings instance
* @param protocolVersion The ProtocolVersion 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 * @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 // Assign the parameters to the class fields
this.protocolServer = protocolServer; this.protocolServer = protocolServer;
this.protocolValues = protocolValues; this.protocolValues = protocolValues;
this.protocolVersion = protocolVersion; this.protocolVersion = protocolVersion;
this.logger = logger;
this.addonLoader = addonLoader;
if (protocolServer instanceof ProtocolINSServer) { if (protocolServer instanceof ProtocolINSServer)
protocolServer.attachBridge(this, null, false, ClientAuthMode.NONE); protocolServer.attachBridge(this, null, false, ClientAuthMode.NONE);
} else else
protocolServer.attachBridge(this, protocolValues.keyPass, protocolValues.ssl, protocolValues.authMode); protocolServer.attachBridge(this, protocolValues.keyPass, protocolValues.ssl, protocolValues.authMode);
// Initialize the logger and protocol version
initializeLogger(logFolder);
initializeProtocolVersion(); initializeProtocolVersion();
downloadLicenses(); downloadLicenses();
// Register the appropriate listeners and packets // Register the appropriate listeners and packets
@@ -107,23 +114,24 @@ public final class ProtocolBridge {
* @param protocolClient The ProtocolClient instance * @param protocolClient The ProtocolClient instance
* @param protocolValues The ProtocolSettings instance * @param protocolValues The ProtocolSettings instance
* @param protocolVersion The ProtocolVersion 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 * @throws Exception if an error occurs while initializing the ProtocolBridge
*/ */
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.CLIENT) @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 // Assign the parameters to the class fields
this.protocolClient = protocolClient; this.protocolClient = protocolClient;
this.protocolValues = protocolValues; this.protocolValues = protocolValues;
this.protocolVersion = protocolVersion; this.protocolVersion = protocolVersion;
this.logger = logger;
this.addonLoader = addonLoader;
protocolClient.attachBridge(this); protocolClient.attachBridge(this);
initializeProtocolVersion();
downloadLicenses(); downloadLicenses();
// Initialize the logger and protocol version
initializeLogger(logFolder);
initializeProtocolVersion();
// Register the appropriate listeners and packets // Register the appropriate listeners and packets
registerListeners(); registerListeners();
@@ -132,8 +140,10 @@ public final class ProtocolBridge {
private void downloadLicenses() throws IOException { private void downloadLicenses() throws IOException {
File licensesFolder = new File("licenses"); File licensesFolder = new File("licenses");
if (!licensesFolder.exists() || !licensesFolder.isDirectory()) { if (!licensesFolder.exists() || !licensesFolder.isDirectory()) {
if (licensesFolder.exists()) licensesFolder.delete(); if (licensesFolder.exists()) licensesFolder.delete();
File output = new File("licenses.zip"); File output = new File("licenses.zip");
output.createNewFile(); output.createNewFile();
FileUtils.downloadFile("https://open-autonomous-connection.org/assets/licenses.zip", output); 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
*
* <p>Overview of all Packets
*
* <table border="2">
* <tr><th>ID</th><th>Packet</th><th>ProtocolVersion</th></tr>
*
* <tr><td>8</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.AuthPacket AuthPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
* <tr><td>7</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket INSQueryPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
* <tr><td>6</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSResponsePacket INSResponsePacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
* <tr><td>10</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket WebRequestPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
* <tr><td>9</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket WebResponsePacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
* <tr><td>11</td><td>{@link WebStreamStartPacket_v1_0_0_B v1_0_0.beta.web.stream.WebStreamStartPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
* <tr><td>13</td><td>{@link WebStreamChunkPacket_v1_0_0_B v1_0_0.beta.web.stream.WebStreamChunkPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
* <tr><td>12</td><td>{@link WebStreamEndPacket_v1_0_0_B v1_0_0.beta.web.stream.WebStreamEndPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
*
* <tr><td>1</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateRequestPacket WebNavigateRequestPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>2</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateAckPacket WebNavigateAckPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>3</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket WebResourceRequestPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>4</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket WebResourceResponsePacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>14</td><td>{@link WebStreamStartPacket_v1_0_1_B v1_0_1.beta.web.impl.stream.WebStreamStartPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>15</td><td>{@link WebStreamChunkPacket_v1_0_1_B v1_0_1.beta.web.impl.stream.WebStreamChunkPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>16</td><td>{@link WebStreamEndPacket_v1_0_1_B v1_0_1.beta.web.impl.stream.WebStreamEndPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>17</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentSnapshotEventPacket WebDocumentSnapshotEventPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>5</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyResponsePacket WebDocumentApplyResponsePacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>18</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyRequestPacket WebDocumentApplyRequestPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* </table>
*
* @see org.openautonomousconnection.protocol.versions.ProtocolVersion
*/ */
private void registerPackets() { private void registerPackets() {
// 1.0.0-BETA packets // 1.0.0-BETA packets
if (isPacketSupported(new AuthPacket(this))) registerPacket(AuthPacket::new);
protocolValues.packetHandler.registerPacket(() -> new AuthPacket(this)); registerPacket(INSQueryPacket::new);
if (isPacketSupported(new INSQueryPacket())) protocolValues.packetHandler.registerPacket(INSQueryPacket::new); registerPacket(() -> new INSResponsePacket(this));
if (isPacketSupported(new INSResponsePacket(this))) registerPacket(WebRequestPacket::new);
protocolValues.packetHandler.registerPacket(() -> new INSResponsePacket(this)); registerPacket(WebResponsePacket::new);
if (isPacketSupported(new WebRequestPacket())) registerPacket(WebStreamStartPacket_v1_0_0_B::new);
protocolValues.packetHandler.registerPacket(WebRequestPacket::new); registerPacket(WebStreamChunkPacket_v1_0_0_B::new);
if (isPacketSupported(new WebResponsePacket())) registerPacket(WebStreamEndPacket_v1_0_0_B::new);
protocolValues.packetHandler.registerPacket(WebResponsePacket::new);
if (isPacketSupported(new WebStreamChunkPacket())) // 1.0.1-BETA Packets
protocolValues.packetHandler.registerPacket(WebStreamChunkPacket::new); registerPacket(WebDocumentApplyRequestPacket::new);
if (isPacketSupported(new WebStreamStartPacket())) registerPacket(WebDocumentApplyResponsePacket::new);
protocolValues.packetHandler.registerPacket(WebStreamStartPacket::new); registerPacket(WebDocumentSnapshotEventPacket::new);
if (isPacketSupported(new WebStreamEndPacket())) registerPacket(WebNavigateRequestPacket::new);
protocolValues.packetHandler.registerPacket(WebStreamEndPacket::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 * 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 * Initialize the protocol version
* Validate if the protocol version is valid for the current side * 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) { public boolean isProtocolSupported(ProtocolVersion.Protocol protocol) {
boolean yes = false; boolean yes = false;
for (ProtocolVersion compatibleVersion : protocolVersion.getCompatibleVersions()) { for (ProtocolVersion compatibleVersion : protocolVersion.getCompatibleVersions()) {
// Check if the compatible version supports the target protocol // Check if the compatible version supports the target protocol
yes = compatibleVersion.getSupportedProtocols().contains(protocol); yes = compatibleVersion.getSupportedProtocols().contains(protocol);
@@ -324,7 +350,7 @@ public final class ProtocolBridge {
* @return true if the current instance is running as a web server, false otherwise * @return true if the current instance is running as a web server, false otherwise
*/ */
public boolean isRunningAsWebServer() { public boolean isRunningAsWebServer() {
return isRunningAsServer() && protocolServer instanceof ProtocolWebServer; return isRunningAsServer() && protocolServer instanceof ProtocolWebServer_1_0_0_B;
} }
/** /**

View File

@@ -1,6 +1,7 @@
package org.openautonomousconnection.protocol.listeners; package org.openautonomousconnection.protocol.listeners;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener; import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
import dev.unlegitdqrk.unlegitlibrary.event.Listener; import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientConnectedEvent; import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientConnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol; import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
@@ -37,7 +38,7 @@ public final class ClientListener extends EventListener {
* *
* @param event The client connected event. * @param event The client connected event.
*/ */
@Listener @Listener(priority = EventPriority.HIGHEST)
public void onConnect(ClientConnectedEvent event) { public void onConnect(ClientConnectedEvent event) {
try { try {
event.getClient().sendPacket(new AuthPacket(client.getProtocolBridge()), TransportProtocol.TCP); event.getClient().sendPacket(new AuthPacket(client.getProtocolBridge()), TransportProtocol.TCP);

View File

@@ -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.ins.ProtocolINSServer;
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient; import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
import org.openautonomousconnection.protocol.side.server.ProtocolCustomServer; 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.INSRecord;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus; 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.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -47,7 +47,7 @@ public final class CustomServerListener extends EventListener {
* *
* @param event The connection handler connected event. * @param event The connection handler connected event.
*/ */
@Listener @Listener(priority = EventPriority.HIGHEST)
public void onConnect(S_ClientConnectedEvent event) { public void onConnect(S_ClientConnectedEvent event) {
try { try {
server.getClients().add(new CustomConnectedClient(event.getClient(), server)); 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) { public void onPacketWeb(S_PacketReadEvent event) {
if (!server.getProtocolBridge().isRunningAsWebServer()) return; if (!server.getProtocolBridge().isRunningAsWebServer()) return;
if (event.getPacket() instanceof WebRequestPacket) { if (event.getPacket() instanceof WebRequestPacket) {
try { try {
event.getClient().sendPacket( event.getClient().sendPacket(
((ProtocolWebServer) server.getProtocolBridge().getProtocolServer()). ((ProtocolWebServer_1_0_0_B) server.getProtocolBridge().getProtocolServer()).
onWebRequest(server.getClientByID(event.getClient().getUniqueID()), (WebRequestPacket) event.getPacket()), onWebRequest(server.getClientByID(event.getClient().getUniqueID()), (WebRequestPacket) event.getPacket()),
TransportProtocol.TCP); TransportProtocol.TCP);
} catch (IOException e) { } catch (IOException e) {
@@ -88,7 +88,7 @@ public final class CustomServerListener extends EventListener {
* *
* @param event The packet event received by the network system. * @param event The packet event received by the network system.
*/ */
@Listener @Listener(priority = EventPriority.HIGHEST)
public void onPacketINS(S_PacketReadEvent event) { public void onPacketINS(S_PacketReadEvent event) {
if (!(event.getPacket() instanceof INSQueryPacket q)) return; if (!(event.getPacket() instanceof INSQueryPacket q)) return;
if (!server.getProtocolBridge().isRunningAsINSServer()) return; if (!server.getProtocolBridge().isRunningAsINSServer()) return;

View File

@@ -55,8 +55,6 @@ public final class AuthPacket extends OACPacket {
@Override @Override
public void onWrite(DataOutputStream objectOutputStream) throws IOException { public void onWrite(DataOutputStream objectOutputStream) throws IOException {
if (protocolBridge.isRunningAsINSServer()) { if (protocolBridge.isRunningAsINSServer()) {
objectOutputStream.writeBoolean(true); objectOutputStream.writeBoolean(true);
objectOutputStream.writeUTF(protocolBridge.getProtocolVersion().name()); objectOutputStream.writeUTF(protocolBridge.getProtocolVersion().name());

View File

@@ -45,7 +45,7 @@ public final class INSQueryPacket extends OACPacket {
* @param clientId Sender client ID for routing. * @param clientId Sender client ID for routing.
*/ */
public INSQueryPacket(String tln, String name, String sub, INSRecordType type, UUID clientId) { 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.TLN = tln;
this.name = name; this.name = name;
this.sub = sub; this.sub = sub;

View File

@@ -46,11 +46,10 @@ public final class INSResponsePacket extends OACPacket {
* @param bridge Protocol runtime context. * @param bridge Protocol runtime context.
*/ */
public INSResponsePacket(INSResponseStatus status, List<INSRecord> records, UUID clientId, ProtocolBridge bridge) { public INSResponsePacket(INSResponseStatus status, List<INSRecord> records, UUID clientId, ProtocolBridge bridge) {
super(6, ProtocolVersion.PV_1_0_0_BETA); this(bridge);
this.status = status; this.status = status;
this.records = records; this.records = records;
this.clientId = clientId; this.clientId = clientId;
this.bridge = bridge;
} }
/** /**

View File

@@ -49,7 +49,7 @@ public final class WebRequestPacket extends OACPacket {
* @param body request body (may be null) * @param body request body (may be null)
*/ */
public WebRequestPacket(String path, WebRequestMethod method, Map<String, String> headers, byte[] body) { public WebRequestPacket(String path, WebRequestMethod method, Map<String, String> headers, byte[] body) {
super(10, ProtocolVersion.PV_1_0_0_BETA); this();
this.path = (path != null) ? path : "/"; this.path = (path != null) ? path : "/";
this.method = (method != null) ? method : WebRequestMethod.GET; this.method = (method != null) ? method : WebRequestMethod.GET;
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap(); this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();

View File

@@ -48,7 +48,7 @@ public final class WebResponsePacket extends OACPacket {
* @param body response body (may be null) * @param body response body (may be null)
*/ */
public WebResponsePacket(int statusCode, String contentType, Map<String, String> headers, byte[] body) { public WebResponsePacket(int statusCode, String contentType, Map<String, String> headers, byte[] body) {
super(9, ProtocolVersion.PV_1_0_0_BETA); this();
this.statusCode = statusCode; this.statusCode = statusCode;
this.contentType = (contentType != null) ? contentType : "text/plain"; this.contentType = (contentType != null) ? contentType : "text/plain";
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap(); this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();

View File

@@ -9,19 +9,19 @@ import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.UUID; import java.util.UUID;
public final class WebStreamChunkPacket extends OACPacket { public final class WebStreamChunkPacket_v1_0_0_B extends OACPacket {
@Getter @Getter
private int seq; private int seq;
@Getter @Getter
private byte[] data; private byte[] data;
public WebStreamChunkPacket() { public WebStreamChunkPacket_v1_0_0_B() {
super(13, ProtocolVersion.PV_1_0_0_BETA); super(13, ProtocolVersion.PV_1_0_0_BETA);
} }
public WebStreamChunkPacket(int seq, byte[] data) { public WebStreamChunkPacket_v1_0_0_B(int seq, byte[] data) {
super(13, ProtocolVersion.PV_1_0_0_BETA); this();
this.seq = seq; this.seq = seq;
this.data = (data != null) ? data : new byte[0]; this.data = (data != null) ? data : new byte[0];
} }

View File

@@ -9,17 +9,17 @@ import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.UUID; import java.util.UUID;
public final class WebStreamEndPacket extends OACPacket { public final class WebStreamEndPacket_v1_0_0_B extends OACPacket {
@Getter @Getter
private boolean ok; private boolean ok;
public WebStreamEndPacket() { public WebStreamEndPacket_v1_0_0_B() {
super(12, ProtocolVersion.PV_1_0_0_BETA); super(13, ProtocolVersion.PV_1_0_0_BETA);
} }
public WebStreamEndPacket(boolean ok) { public WebStreamEndPacket_v1_0_0_B(boolean ok) {
super(12, ProtocolVersion.PV_1_0_0_BETA); this();
this.ok = ok; this.ok = ok;
} }

View File

@@ -18,7 +18,7 @@ import java.util.UUID;
* <p>Important: This packet encodes headers using a deterministic UTF-based map format * <p>Important: This packet encodes headers using a deterministic UTF-based map format
* (int size, then size times: UTF key, UTF value) instead of Java object serialization.</p> * (int size, then size times: UTF key, UTF value) instead of Java object serialization.</p>
*/ */
public final class WebStreamStartPacket extends OACPacket { public final class WebStreamStartPacket_v1_0_0_B extends OACPacket {
@Getter @Getter
private int statusCode; private int statusCode;
@@ -35,7 +35,7 @@ public final class WebStreamStartPacket extends OACPacket {
/** /**
* Creates an empty start packet (used by PacketHandler factory). * 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); 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 headers headers (may be null)
* @param totalLength total length of the stream (may be -1 if unknown) * @param totalLength total length of the stream (may be -1 if unknown)
*/ */
public WebStreamStartPacket(int statusCode, String contentType, Map<String, String> headers, long totalLength) { public WebStreamStartPacket_v1_0_0_B(int statusCode, String contentType, Map<String, String> headers, long totalLength) {
super(11, ProtocolVersion.PV_1_0_0_BETA); this();
this.statusCode = statusCode; this.statusCode = statusCode;
this.contentType = (contentType != null) ? contentType : "application/octet-stream"; this.contentType = (contentType != null) ? contentType : "application/octet-stream";
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap(); this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();

View File

@@ -0,0 +1,67 @@
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.UUID;
/**
* Base class for all Web v1.0.1-BETA packets.
*
* <p>Ensures the {@link WebPacketHeader} is always serialized first.</p>
*/
@Getter
public abstract class WebPacket extends OACPacket {
private WebPacketHeader header;
protected WebPacket(int packetId, ProtocolVersion version) {
super(packetId, version);
}
/**
* Sets the header before sending.
*
* @param header header (required)
*/
public void setHeader(WebPacketHeader header) {
this.header = header;
}
@Override
public final void onWrite(DataOutputStream out) throws IOException {
if (header == null) {
throw new IOException("WebPacketHeader is required");
}
header.write(out);
writeBody(out);
}
@Override
public final void onRead(DataInputStream in, UUID clientID) throws IOException {
this.header = WebPacketHeader.read(in);
readBody(in, clientID);
}
/**
* Writes packet-specific payload after the header.
*
* @param out stream
* @throws IOException on IO error
*/
protected abstract void writeBody(DataOutputStream out) throws IOException;
/**
* Reads packet-specific payload after the header.
*
* @param in stream
* @param clientID sender client id
* @throws IOException on IO error
*/
protected abstract void readBody(DataInputStream in, UUID clientID) throws IOException;
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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<String, String> 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<String, String> 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);
}
}

View File

@@ -0,0 +1,83 @@
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
/**
* Resource response
*
* <p>If the response body is streamed, send this packet with an empty body and set STREAM flag in the header.</p>
*/
@Getter
public final class WebResourceResponsePacket extends WebPacket {
private int statusCode;
private String contentType;
private Map<String, String> headers;
private byte[] body;
private String finalUrl;
/**
* Registration constructor.
*/
public WebResourceResponsePacket() {
super(4, ProtocolVersion.PV_1_0_1_BETA);
}
public WebResourceResponsePacket(
WebPacketHeader header,
int statusCode,
String contentType,
Map<String, String> headers,
byte[] body,
String finalUrl
) {
this();
setHeader(header);
this.statusCode = statusCode;
this.contentType = (contentType != null) ? contentType : "application/octet-stream";
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
this.body = (body != null) ? body : new byte[0];
this.finalUrl = finalUrl;
}
@Override
protected void writeBody(DataOutputStream out) throws IOException {
out.writeInt(statusCode);
out.writeUTF((contentType != null) ? contentType : "application/octet-stream");
writeStringMap(out, headers);
out.writeBoolean(finalUrl != null);
if (finalUrl != null) out.writeUTF(finalUrl);
byte[] b = (body != null) ? body : new byte[0];
out.writeInt(b.length);
out.write(b);
}
@Override
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
this.statusCode = in.readInt();
this.contentType = in.readUTF();
this.headers = readStringMap(in);
boolean hasFinal = in.readBoolean();
this.finalUrl = hasFinal ? in.readUTF() : null;
int len = in.readInt();
if (len < 0) throw new IOException("Negative body length in WebResourceResponsePacket");
this.body = in.readNBytes(len);
}
}

View File

@@ -0,0 +1,52 @@
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.UUID;
/**
* Stream body chunk.
*
* <p>Correlation is done via {@link WebPacketHeader#getRequestId()}.</p>
*/
@Getter
public final class WebStreamChunkPacket_v1_0_1_B extends WebPacket {
private int seq;
private byte[] data;
/**
* Registration constructor.
*/
public WebStreamChunkPacket_v1_0_1_B() {
super(15, ProtocolVersion.PV_1_0_1_BETA);
}
public WebStreamChunkPacket_v1_0_1_B(WebPacketHeader header, int seq, byte[] data) {
this();
setHeader(header);
this.seq = seq;
this.data = (data != null) ? data : new byte[0];
}
@Override
protected void writeBody(DataOutputStream out) throws IOException {
out.writeInt(seq);
byte[] b = (data != null) ? data : new byte[0];
out.writeInt(b.length);
out.write(b);
}
@Override
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
this.seq = in.readInt();
int len = in.readInt();
if (len < 0) throw new IOException("Negative chunk length in WebStreamChunkPacket");
this.data = in.readNBytes(len);
}
}

View File

@@ -0,0 +1,50 @@
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.UUID;
/**
* Ends a streamed body transfer.
*
* <p>Correlation is done via {@link WebPacketHeader#getRequestId()}.</p>
*/
@Getter
public final class WebStreamEndPacket_v1_0_1_B extends WebPacket {
private boolean ok;
private String error;
/**
* Registration constructor.
*/
public WebStreamEndPacket_v1_0_1_B() {
super(16, ProtocolVersion.PV_1_0_1_BETA);
}
public WebStreamEndPacket_v1_0_1_B(WebPacketHeader header, boolean ok, String error) {
this();
setHeader(header);
this.ok = ok;
this.error = error;
}
@Override
protected void writeBody(DataOutputStream out) throws IOException {
out.writeBoolean(ok);
out.writeBoolean(error != null);
if (error != null) out.writeUTF(error);
}
@Override
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
this.ok = in.readBoolean();
boolean hasErr = in.readBoolean();
this.error = hasErr ? in.readUTF() : null;
}
}

View File

@@ -0,0 +1,59 @@
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
/**
* Starts a streamed response body.
*
* <p>Correlation is done via {@link WebPacketHeader#getRequestId()}.</p>
*/
@Getter
public final class WebStreamStartPacket_v1_0_1_B extends WebPacket {
private int statusCode;
private String contentType;
private Map<String, String> headers;
private long totalLength;
/**
* Registration constructor.
*/
public WebStreamStartPacket_v1_0_1_B() {
super(14, ProtocolVersion.PV_1_0_1_BETA);
}
public WebStreamStartPacket_v1_0_1_B(WebPacketHeader header, int statusCode, String contentType, Map<String, String> headers, long totalLength) {
this();
setHeader(header);
this.statusCode = statusCode;
this.contentType = (contentType != null) ? contentType : "application/octet-stream";
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
this.totalLength = totalLength;
}
@Override
protected void writeBody(DataOutputStream out) throws IOException {
out.writeInt(statusCode);
out.writeUTF((contentType != null) ? contentType : "application/octet-stream");
writeStringMap(out, headers);
out.writeLong(totalLength);
}
@Override
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
this.statusCode = in.readInt();
this.contentType = in.readUTF();
this.headers = readStringMap(in);
this.totalLength = in.readLong();
}
}

View File

@@ -1,6 +1,7 @@
package org.openautonomousconnection.protocol.side.client; package org.openautonomousconnection.protocol.side.client;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener; import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
import dev.unlegitdqrk.unlegitlibrary.event.Listener; import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils; import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient; 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; if (this.insVersion == null) this.insVersion = insVersion;
} }
@Listener @Listener(priority = EventPriority.HIGHEST)
public final void onDisconnect(ClientDisconnectedEvent event) { public final void onDisconnect(ClientDisconnectedEvent event) {
if (clientToINS == null || !clientToINS.isConnected()) { if (clientToINS == null || !clientToINS.isConnected()) {
insVersion = null; insVersion = null;
@@ -182,7 +183,7 @@ public abstract class ProtocolClient extends EventListener {
disconnectFromServer(); disconnectFromServer();
} }
if (clientToServer != null && !clientToServer.isConnected()) { if (clientToServer == null || !clientToServer.isConnected()) {
serverVersion = null; serverVersion = null;
clientToServer = null; clientToServer = null;
} }
@@ -254,7 +255,7 @@ public abstract class ProtocolClient extends EventListener {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE; yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE;
if (yes) break; if (yes) break;
} }
return isBetaServer() || yes; return isStableServer() || yes;
} }
public final boolean isBetaServer() { public final boolean isBetaServer() {
@@ -267,7 +268,7 @@ public abstract class ProtocolClient extends EventListener {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA; yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA;
if (yes) break; if (yes) break;
} }
return isStableServer() || yes; return isBetaServer() || yes;
} }
public final boolean isClassicServer() { public final boolean isClassicServer() {
@@ -312,6 +313,7 @@ public abstract class ProtocolClient extends EventListener {
private final void disconnectFromServer() { private final void disconnectFromServer() {
if (clientToServer != null) { if (clientToServer != null) {
if (clientToINS == null || !clientToINS.isConnected())
protocolBridge.getProtocolValues().eventManager.unregisterListener(this); protocolBridge.getProtocolValues().eventManager.unregisterListener(this);
clientToServer.disconnect(); clientToServer.disconnect();
clientToServer = null; clientToServer = null;

View File

@@ -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}.
*
* <p>Compatibility strategy:</p>
* <ul>
* <li>In Web v1.0.0 mode, correlation is only possible if there is exactly ONE in-flight request.</li>
* <li>This client therefore enforces single in-flight request and maps responses/streams
* onto that single active {@link WebPacketHeader}.</li>
* </ul>
*/
public abstract class ProtocolWebClient extends ProtocolClient {
/**
* Web header factory bound to this client instance.
*/
@Getter
private final WebHeaderFactory webHeaderFactory = new WebHeaderFactory();
/**
* Tab contexts by tabId (stable).
*/
private final Map<Long, WebHeaderFactory.WebTabContext> tabs = new ConcurrentHashMap<>();
/**
* Single in-flight correlation header.
*
* <p>v1.0.0 has no correlation fields; without changing the v1.0.0 server this is the only deterministic mapping.</p>
*/
private final AtomicReference<WebPacketHeader> v100BInFlight = new AtomicReference<>();
/**
* True once we observed a 1.0.0 stream start for the current in-flight request.
*/
private volatile boolean v100BStreaming;
/**
* Creates and registers a new tab context.
*/
public final WebHeaderFactory.WebTabContext createTab() {
WebHeaderFactory.WebTabContext tab = webHeaderFactory.createTab();
tabs.put(tab.getTabId(), tab);
return tab;
}
/**
* Removes a tab context.
*/
public final void closeTab(long tabId) {
tabs.remove(tabId);
}
/**
* Returns the tab context for a tab id.
*/
public final WebHeaderFactory.WebTabContext getTab(long tabId) {
return tabs.get(tabId);
}
/**
* Begins a new navigation for the given tab.
*/
public final long beginNavigation(WebHeaderFactory.WebTabContext tab) {
Objects.requireNonNull(tab, "tab");
return webHeaderFactory.beginNavigation(tab);
}
public final void sendNavigate(
WebHeaderFactory.WebTabContext tab,
String url,
String referrer,
WebTransitionType transition,
long headerProfileId,
WebCacheMode cacheMode,
boolean noStore
) throws Exception {
Objects.requireNonNull(tab, "tab");
ensureServerConnected();
WebPacketHeader header = webHeaderFactory.navigation(tab, noStore);
header = new WebPacketHeader(
header.getRequestId(),
header.getTabId(),
header.getPageId(),
header.getFrameId(),
header.getFlags() | WebPacketFlags.NAVIGATION,
header.getTimestampMs()
);
if (is100BServer()) {
begin100BRequest(header);
WebRequestPacket packet = new WebRequestPacket(
url,
WebRequestMethod.GET,
Collections.emptyMap(),
null
);
getClientServerConnection().sendPacket(packet, TransportProtocol.TCP);
onWebRequestSent(null);
return;
}
WebNavigateRequestPacket pkt = new WebNavigateRequestPacket(
header,
url,
referrer,
transition,
headerProfileId,
cacheMode
);
getClientServerConnection().sendPacket(pkt, TransportProtocol.TCP);
onWebRequestSent(pkt);
}
public final long sendResource(
WebHeaderFactory.WebTabContext tab,
long frameId,
String url,
String method,
Map<String, String> headers,
byte[] body,
String bodyContentType,
WebInitiatorType initiator,
WebCacheMode cacheMode,
boolean noStore
) throws Exception {
Objects.requireNonNull(tab, "tab");
ensureServerConnected();
WebPacketHeader base = webHeaderFactory.resource(tab, frameId, noStore);
WebPacketHeader header = new WebPacketHeader(
base.getRequestId(),
base.getTabId(),
base.getPageId(),
base.getFrameId(),
base.getFlags() | WebPacketFlags.RESOURCE,
base.getTimestampMs()
);
if (is100BServer()) {
begin100BRequest(header);
WebRequestPacket packet = new WebRequestPacket(
url,
WebCompatMapper.map100BMethod(method),
headers != null ? headers : Collections.emptyMap(),
body
);
getClientServerConnection().sendPacket(packet, TransportProtocol.TCP);
onWebRequestSent(null);
return header.getRequestId();
}
WebResourceRequestPacket pkt = new WebResourceRequestPacket(
header,
url,
method,
headers,
body,
bodyContentType,
initiator,
cacheMode
);
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.
*
* <p>Enforces single in-flight 1.0.0 request.</p>
*
* @param correlation correlation header to use for mapping 1.0.0 responses
*/
private void begin100BRequest(WebPacketHeader correlation) {
Objects.requireNonNull(correlation, "correlation");
v100BStreaming = false;
if (!v100BInFlight.compareAndSet(null, correlation)) {
throw new IllegalStateException(
"Web v1.0.0 mode supports only 1 in-flight request. " +
"Wait for response/stream end before sending the next request."
);
}
}
/**
* Releases 1.0.0 correlation state.
*/
private void end100BRequest() {
v100BStreaming = false;
v100BInFlight.set(null);
}
/**
* Heuristic: checks if a 1.0.0 response indicates streaming will follow.
*
* <p>Your v1.0.0 WebServer uses: header 'x-oac-stream' = '1' for the 202 response before streaming.</p>
*
* @param resp 1.0.0 response
* @return true if likely stream handshake
*/
private boolean looksLike100BStreamHandshake(WebResponsePacket resp) {
if (resp == null) return false;
Map<String, String> h = resp.getHeaders();
if (h == null) return false;
// Case-insensitive lookup without allocating
for (Map.Entry<String, String> e : h.entrySet()) {
if (e.getKey() != null && e.getValue() != null
&& e.getKey().equalsIgnoreCase("x-oac-stream")
&& e.getValue().equals("1")) {
return true;
}
}
return false;
}
}

View File

@@ -38,8 +38,8 @@ public class CustomConnectedClient extends EventListener {
clientVersion = null; clientVersion = null;
} }
@Listener(priority = EventPriority.HIGH) @Listener(priority = EventPriority.LOWEST)
public void onDisconnect(S_ClientDisconnectedEvent event) { public final void onDisconnect(S_ClientDisconnectedEvent event) {
if (event.getClient().getUniqueID().equals(this.connection.getUniqueID())) { if (event.getClient().getUniqueID().equals(this.connection.getUniqueID())) {
server.getProtocolBridge().getProtocolValues().eventManager.unregisterListener(this); server.getProtocolBridge().getProtocolValues().eventManager.unregisterListener(this);
clientVersion = null; clientVersion = null;

View File

@@ -120,7 +120,7 @@ public abstract class ProtocolCustomServer extends EventListener {
trustStore.setCertificateEntry("root-ca-" + (caIndex++), caCert); 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 keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null); keyStore.load(null, null);
@@ -209,8 +209,8 @@ public abstract class ProtocolCustomServer extends EventListener {
} }
} }
@Listener @Listener(priority = EventPriority.HIGHEST)
public void onStop(ServerStoppedEvent event) { public final void onStop(ServerStoppedEvent event) {
if (event.getServer() == network) { if (event.getServer() == network) {
protocolBridge.getProtocolValues().eventManager.unregisterListener(this); 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) { public final void clientDisconnected(S_ClientDisconnectedEvent event) {
for (CustomConnectedClient client : new ArrayList<>(clients)) { for (CustomConnectedClient client : new ArrayList<>(clients)) {
if (client.getConnection().getUniqueID().equals(event.getClient().getUniqueID())) { if (client.getConnection().getUniqueID().equals(event.getClient().getUniqueID())) {

View File

@@ -1,131 +1,220 @@
package org.openautonomousconnection.protocol.side.web; 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.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket; 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.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.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.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.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.
*
* <p>This class keeps the v1.0.0 entry point ({@link #onWebRequest(CustomConnectedClient, WebRequestPacket)})
* and adapts it to the v1.0.1 resource pipeline by mapping v1.0.0 requests to a synthetic
* {@link WebResourceRequestPacket} and mapping v1.0.1 responses back to {@link WebResponsePacket}.</p>
*
* <p>Important:
* <ul>
* <li>This class does NOT perform network packet dispatch automatically. Your server packet-receive listener
* must call the appropriate {@code handle*} methods (v1.0.1) or let the v1.0.0 pipeline call {@code onWebRequest}.</li>
* <li>Responses/streams must preserve correlation via {@link WebPacketHeader#getRequestId()} for v1.0.1 clients.</li>
* <li>v1.0.0 streaming packets cannot carry request correlation. If you stream, you must
* send 1.0.0 stream packets to 1.0.0 clients and v1.0.1 stream packets to v1.0.1 clients.</li>
* </ul>
* </p>
*/ */
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB) @ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
public abstract class ProtocolWebServer extends ProtocolCustomServer { public abstract class ProtocolWebServer extends ProtocolWebServer_1_0_0_B {
/**
* Folder for web content. private static final long LEGACY_TAB_ID = 1L;
*/ private static final long LEGACY_PAGE_ID = 1L;
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. * Initializes the web server with the given configuration, authentication, and rules files.
* *
* @param authFile The authentication file. * @param authFile The authentication file.
* @param rulesFile The rules file. * @param rulesFile The rules file.
* @param sessionExpire The expiration time of a Session in minutes * @param sessionExpire The expiration time of a Session in minutes.
* @param uploadSize The max upload size in MB * @param uploadSize The max upload size in MB.
* @throws Exception If an error occurs during initialization. * @throws Exception If an error occurs during initialization.
*/ */
public ProtocolWebServer(File authFile, File rulesFile, int sessionExpire, int uploadSize) throws Exception { public ProtocolWebServer(File authFile, File rulesFile, int sessionExpire, int uploadSize) throws Exception {
super("server", "server"); super(authFile, rulesFile, sessionExpire, uploadSize);
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. * Server-side dispatcher entry point for a navigation request (packet id 01).
* *
* @param client The connected web client (pipeline + web socket). * @param client connected client
* @param request The full decoded request packet. * @param request navigation request
* @return The response packet that should be sent back to the client. * @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).
*
* <p>Streaming:
* <ul>
* <li>If you want to stream the body, return a {@link WebResourceResponsePacket} with an empty body,
* and then send {@link WebStreamStartPacket_v1_0_1_B}/{@link WebStreamChunkPacket_v1_0_1_B}/{@link WebStreamEndPacket_v1_0_1_B}
* using the SAME {@code requestId}.</li>
* </ul>
* </p>
*
* @param client connected client
* @param request resource request
* @return resource response packet to send back
*/
public final WebResourceResponsePacket handleResource(CustomConnectedClient client, WebResourceRequestPacket request) {
Objects.requireNonNull(client, "client");
Objects.requireNonNull(request, "request");
WebResourceResponsePacket resp = onResourceRequest(client, request);
if (resp == null) {
resp = new WebResourceResponsePacket(
mirrorHeader(request.getHeader(), WebPacketFlags.RESOURCE),
500,
"text/plain; charset=utf-8",
java.util.Collections.emptyMap(),
"onResourceRequest returned null".getBytes(StandardCharsets.UTF_8),
null
);
}
return resp;
}
/**
* Server-side dispatcher entry point for a document apply request (packet id 20).
*
* @param client connected client
* @param request apply request
* @return apply response
*/
public final WebDocumentApplyResponsePacket handleDocumentApply(CustomConnectedClient client, WebDocumentApplyRequestPacket request) {
Objects.requireNonNull(client, "client");
Objects.requireNonNull(request, "request");
WebDocumentApplyResponsePacket resp = onDocumentApplyRequest(client, request);
if (resp == null) {
resp = new WebDocumentApplyResponsePacket(
mirrorHeader(request.getHeader(), 0),
false,
"onDocumentApplyRequest returned null"
);
}
return resp;
}
/**
* Hook: called when the server receives a navigation request.
*
* @param client connected client
* @param request navigation request
* @return ack packet (must include mirrored requestId/tabId/pageId/frameId)
*/
protected abstract WebNavigateAckPacket onNavigateRequest(CustomConnectedClient client, WebNavigateRequestPacket request);
/**
* Hook: called when the server receives a resource request.
*
* @param client connected client
* @param request resource request
* @return response packet (must include mirrored requestId/tabId/pageId/frameId)
*/
protected abstract WebResourceResponsePacket onResourceRequest(CustomConnectedClient client, WebResourceRequestPacket request);
/**
* Hook: called when the server receives a document apply request.
*
* @param client connected client
* @param request apply request
* @return apply response packet (must include mirrored requestId/tabId/pageId/frameId)
*/
protected abstract WebDocumentApplyResponsePacket onDocumentApplyRequest(CustomConnectedClient client, WebDocumentApplyRequestPacket request);
/**
* v1.0.0 entry point.
*
* <p>This method is invoked by the v1.0.0 server pipeline when a 1.0.0 client sends a 1.0.0 request packet.
* We adapt it into the v1.0.1 resource pipeline.</p>
*
* @param client connected client
* @param request 1.0.0 request packet
* @return 1.0.0 response packet
*/
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
@Override
public WebResponsePacket onWebRequest(CustomConnectedClient client, WebRequestPacket request) {
Objects.requireNonNull(client, "client");
Objects.requireNonNull(request, "request");
// 1.0.0 requests have no correlation fields. Create synthetic ones.
long requestId = WebCompatMapper.nextCompatRequestId();
long tabId = LEGACY_TAB_ID;
long pageId = LEGACY_PAGE_ID;
WebResourceRequestPacket mapped = WebCompatMapper.toResourceRequest(requestId, tabId, pageId, request);
WebResourceResponsePacket resp = onResourceRequest(client, mapped);
if (resp == null) {
return new WebResponsePacket(
500,
"text/plain; charset=utf-8",
java.util.Collections.emptyMap(),
"onResourceRequest returned null".getBytes(StandardCharsets.UTF_8)
);
}
return WebCompatMapper.to100BResponse(resp);
}
/**
* Creates a response header that mirrors correlation fields from an incoming header.
*
* @param incoming incoming header
* @param extraFlags extra flags to OR into the mirrored header
* @return mirrored header
*/
protected final WebPacketHeader mirrorHeader(WebPacketHeader incoming, int extraFlags) {
Objects.requireNonNull(incoming, "incoming");
return new WebPacketHeader(
incoming.getRequestId(),
incoming.getTabId(),
incoming.getPageId(),
incoming.getFrameId(),
incoming.getFlags() | extraFlags,
System.currentTimeMillis()
);
}
} }

View File

@@ -12,14 +12,19 @@ import java.util.List;
*/ */
public enum ProtocolVersion implements Serializable { public enum ProtocolVersion implements Serializable {
/** /**
* Support for old OAC-Project => <a href="https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/">*_old</a> * For classic OAC-Project => <a href="https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/">"classic"-branch</a>
*/ */
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
PV_1_0_0_CLASSIC("1.0.0", ProtocolType.CLASSIC, ProtocolSide.WEB_INS, List.of(Protocol.HTTP)), PV_1_0_0_CLASSIC("1.0.0", ProtocolType.CLASSIC, ProtocolSide.WEB_INS, List.of(Protocol.HTTP)),
/** /**
* First Beta Version of OAC-Protocol * Version {@link ProtocolVersion#PV_1_0_0_BETA} has many bugs and does not work as expected (occurred by incompleted packets).
* Use {@link ProtocolVersion#PV_1_0_1_BETA} or newer.
*/ */
PV_1_0_0_BETA("1.0.0", ProtocolType.BETA, ProtocolSide.ALL, List.of(Protocol.OAC), 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),
; ;
/** /**

View File

@@ -149,9 +149,7 @@ public final class INSRecordTools {
return record.ttl >= 0; return record.ttl >= 0;
} }
// -------------------------------------------------------------------------
// Validation helpers // Validation helpers
// -------------------------------------------------------------------------
/** /**
* Validates a collection of records by applying {@link #isValidRecord(INSRecord)} * Validates a collection of records by applying {@link #isValidRecord(INSRecord)}

View File

@@ -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);
}

View File

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

View File

@@ -5,7 +5,7 @@ import java.io.Serializable;
/** /**
* Enum representing the protocol versions for the Classic protocol. * Enum representing the protocol versions for the Classic protocol.
*/ */
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3") @Deprecated(forRemoval = true, since = "1.0.1-BETA.0.1")
public enum Classic_ProtocolVersion implements Serializable { public enum Classic_ProtocolVersion implements Serializable {
PV_1_0_0("1.0.0"); PV_1_0_0("1.0.0");
public final String version; public final String version;

View File

@@ -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
}

View File

@@ -0,0 +1,250 @@
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
import java.security.SecureRandom;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
/**
* Factory for creating {@link WebPacketHeader} instances with stable tab/page identifiers and
* monotonic request identifiers.
*
* <p>This class is intended to be used by the WebClient/WebServer integration layer to ensure
* every packet carries a valid header and consistent correlation metadata.</p>
*
* <p>Thread-safety:
* <ul>
* <li>Request id generation is thread-safe.</li>
* <li>Tab/page allocation methods are thread-safe.</li>
* <li>{@link WebTabContext} is thread-safe for reads; writes are synchronized.</li>
* </ul>
* </p>
*/
public final class WebHeaderFactory {
private static final long MIN_ID = 1L;
private final AtomicLong requestIdSeq;
private final AtomicLong tabIdSeq;
private final AtomicLong pageIdSeq;
/**
* Creates a factory with randomized id bases to reduce collisions across process restarts.
*/
public WebHeaderFactory() {
SecureRandom rnd = new SecureRandom();
this.requestIdSeq = new AtomicLong(Math.max(MIN_ID, rnd.nextLong() & Long.MAX_VALUE));
this.tabIdSeq = new AtomicLong(Math.max(MIN_ID, rnd.nextLong() & Long.MAX_VALUE));
this.pageIdSeq = new AtomicLong(Math.max(MIN_ID, rnd.nextLong() & Long.MAX_VALUE));
}
/**
* Creates a new tab context with a fresh tab id and an initial page id.
*
* @return new tab context
*/
public WebTabContext createTab() {
long tabId = nextTabId();
long pageId = nextPageId();
return new WebTabContext(tabId, pageId);
}
/**
* Starts a new navigation by allocating a new page id for the given tab context.
*
* <p>Call this when navigation is initiated (typed URL, link click, reload, back/forward).</p>
*
* @param tab tab context
* @return the new page id
*/
public long beginNavigation(WebTabContext tab) {
Objects.requireNonNull(tab, "tab");
long newPageId = nextPageId();
tab.setPageId(newPageId);
return newPageId;
}
/**
* Creates a header for a navigation request.
*
* @param tab tab context
* @param noStore if true, sets {@link WebPacketFlags#NO_STORE}
* @return header
*/
public WebPacketHeader navigation(WebTabContext tab, boolean noStore) {
Objects.requireNonNull(tab, "tab");
int flags = WebPacketFlags.NAVIGATION;
if (noStore) flags |= WebPacketFlags.NO_STORE;
return header(nextRequestId(), tab.getTabId(), tab.getPageId(), 0L, flags);
}
/**
* Creates a header for a resource request.
*
* @param tab tab context
* @param frameId frame id (0 = main)
* @param noStore if true, sets {@link WebPacketFlags#NO_STORE}
* @return header
*/
public WebPacketHeader resource(WebTabContext tab, long frameId, boolean noStore) {
Objects.requireNonNull(tab, "tab");
int flags = WebPacketFlags.RESOURCE;
if (noStore) flags |= WebPacketFlags.NO_STORE;
return header(nextRequestId(), tab.getTabId(), tab.getPageId(), frameId, flags);
}
/**
* Creates a header for a resource response correlated to a request id.
*
* @param requestId request id to correlate
* @param tabId tab id
* @param pageId page id
* @param frameId frame id
* @return header
*/
public WebPacketHeader resourceResponse(long requestId, long tabId, long pageId, long frameId) {
return header(requestId, tabId, pageId, frameId, WebPacketFlags.RESOURCE);
}
/**
* Creates a header for a streamed payload associated with a request id.
*
* @param requestId correlated request id
* @param tabId tab id
* @param pageId page id
* @param frameId frame id
* @return header
*/
public WebPacketHeader stream(long requestId, long tabId, long pageId, long frameId) {
return header(requestId, tabId, pageId, frameId, WebPacketFlags.STREAM);
}
/**
* Creates an event header (server->client or client->server).
*
* @param tabId tab id
* @param pageId page id
* @param frameId frame id
* @return header
*/
public WebPacketHeader event(long tabId, long pageId, long frameId) {
return header(nextRequestId(), tabId, pageId, frameId, WebPacketFlags.EVENT);
}
/**
* Creates an event header correlated to an existing request id.
*
* @param requestId request id
* @param tabId tab id
* @param pageId page id
* @param frameId frame id
* @return header
*/
public WebPacketHeader correlatedEvent(long requestId, long tabId, long pageId, long frameId) {
return header(requestId, tabId, pageId, frameId, WebPacketFlags.EVENT);
}
/**
* Creates a header for a document snapshot event.
*
* @param tab tab context
* @param frameId frame id
* @return header
*/
public WebPacketHeader documentSnapshotEvent(WebTabContext tab, long frameId) {
Objects.requireNonNull(tab, "tab");
return header(nextRequestId(), tab.getTabId(), tab.getPageId(), frameId, WebPacketFlags.EVENT);
}
/**
* Creates a header for a document apply request.
*
* @param tab tab context
* @param frameId frame id
* @return header
*/
public WebPacketHeader documentApply(WebTabContext tab, long frameId) {
Objects.requireNonNull(tab, "tab");
return header(nextRequestId(), tab.getTabId(), tab.getPageId(), frameId, WebPacketFlags.RESOURCE);
}
/**
* Checks whether an incoming packet is stale for a given tab context.
*
* <p>Stale means: the packet refers to an older pageId than the current tab pageId.</p>
*
* @param tab tab context
* @param incoming incoming header
* @return true if stale, false otherwise
*/
public boolean isStale(WebTabContext tab, WebPacketHeader incoming) {
Objects.requireNonNull(tab, "tab");
Objects.requireNonNull(incoming, "incoming");
return incoming.getTabId() == tab.getTabId() && incoming.getPageId() != tab.getPageId();
}
private WebPacketHeader header(long requestId, long tabId, long pageId, long frameId, int flags) {
long ts = System.currentTimeMillis();
return new WebPacketHeader(requestId, tabId, pageId, frameId, flags, ts);
}
private long nextRequestId() {
long v = requestIdSeq.incrementAndGet();
return (v < MIN_ID) ? MIN_ID : v;
}
private long nextTabId() {
long v = tabIdSeq.incrementAndGet();
return (v < MIN_ID) ? MIN_ID : v;
}
private long nextPageId() {
long v = pageIdSeq.incrementAndGet();
return (v < MIN_ID) ? MIN_ID : v;
}
/**
* Represents per-tab state used for header creation and stale detection.
*/
public static final class WebTabContext {
private final long tabId;
private volatile long pageId;
/**
* Creates a tab context.
*
* @param tabId tab id
* @param pageId initial page id
*/
public WebTabContext(long tabId, long pageId) {
if (tabId < MIN_ID) throw new IllegalArgumentException("tabId must be >= 1");
if (pageId < MIN_ID) throw new IllegalArgumentException("pageId must be >= 1");
this.tabId = tabId;
this.pageId = pageId;
}
/**
* @return stable tab id
*/
public long getTabId() {
return tabId;
}
/**
* @return current page id
*/
public long getPageId() {
return pageId;
}
/**
* Sets the current page id.
*
* @param pageId new page id
*/
public synchronized void setPageId(long pageId) {
if (pageId < MIN_ID) throw new IllegalArgumentException("pageId must be >= 1");
this.pageId = pageId;
}
}
}

View File

@@ -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
}

View File

@@ -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;
}
}

View File

@@ -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.
*
* <p>This header MUST be written/read first in every web packet.</p>
*/
@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);
}
}

View File

@@ -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
}

View File

@@ -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).
*
* <p>This class provides pure mapping logic and contains no networking code.</p>
*
* <p>Usage:
* <ul>
* <li>Server-side: map 1.0.0 {@link WebRequestPacket} to {@link WebResourceRequestPacket}</li>
* <li>Server-side: map {@link WebResourceResponsePacket} back to {@link WebResponsePacket}</li>
* <li>Client-side: map 1.0.0 responses/streams to v1.0.1 packets so v1.0.1 hooks can be reused</li>
* </ul>
* </p>
*/
public final class WebCompatMapper {
/**
* Synthetic request id generator for 1.0.0 requests.
*/
private static final AtomicLong COMPAT_REQUEST_ID = new AtomicLong(1L);
private WebCompatMapper() {
}
/**
* Generates a monotonic synthetic request id.
*
* @return next request id
*/
public static long nextCompatRequestId() {
return COMPAT_REQUEST_ID.getAndIncrement();
}
/**
* Maps a v1.0.0 {@link WebRequestPacket} to a v1.0.1 {@link WebResourceRequestPacket}.
*
* <p>Because v1.0.0 has no tab/page/frame/request correlation model,
* synthetic identifiers must be provided.</p>
*
* @param requestId synthetic request id
* @param tabId synthetic tab id
* @param pageId synthetic page id
* @param requestPacket 1.0.0 request
* @return mapped v1.0.1 resource request
*/
public static WebResourceRequestPacket toResourceRequest(
long requestId,
long tabId,
long pageId,
WebRequestPacket requestPacket
) {
Objects.requireNonNull(requestPacket, "requestPacket");
String path = requestPacket.getPath();
String method = mapMethod(requestPacket.getMethod());
Map<String, String> headers = requestPacket.getHeaders() != null ? requestPacket.getHeaders() : Collections.emptyMap();
byte[] body = requestPacket.getBody() != null ? requestPacket.getBody() : new byte[0];
WebPacketHeader header = new WebPacketHeader(
requestId,
tabId,
pageId,
0L,
WebPacketFlags.RESOURCE,
System.currentTimeMillis()
);
return new WebResourceRequestPacket(
header,
path,
method,
headers,
body,
null,
WebInitiatorType.OTHER,
WebCacheMode.DEFAULT
);
}
/**
* Maps a v1.0.1 {@link WebResourceResponsePacket} back to a 1.0.0 {@link WebResponsePacket}.
*
* <p>Correlation information is lost because v1.0.0 does not support it.</p>
*
* @param response v1.0.1 resource response
* @return 1.0.0 response packet
*/
public static WebResponsePacket to100BResponse(WebResourceResponsePacket response) {
Objects.requireNonNull(response, "response");
return new WebResponsePacket(
response.getStatusCode(),
response.getContentType(),
response.getHeaders(),
response.getBody()
);
}
/**
* Maps v1.0.1 resource response to 1.0.0 response with overridden body.
*
* <p>Useful when buffering streamed responses for 1.0.0 clients.</p>
*
* @param response original v1.0.1 response
* @param body buffered full body
* @return 1.0.0 response
*/
public static WebResponsePacket to100BResponse(WebResourceResponsePacket response, byte[] body) {
Objects.requireNonNull(response, "response");
return new WebResponsePacket(
response.getStatusCode(),
response.getContentType(),
response.getHeaders(),
body != null ? body : new byte[0]
);
}
/**
* Maps a v1.0.0 {@link WebResponsePacket} to a v1.0.1 {@link WebResourceResponsePacket}
* using the provided synthetic correlation header.
*
* @param correlationHeader synthetic correlation header (requestId/tabId/pageId/frameId)
* @param responsePacket 1.0.0 response packet
* @return v1.0.1 resource response packet
*/
public static WebResourceResponsePacket toV101ResourceResponse(WebPacketHeader correlationHeader, WebResponsePacket responsePacket) {
Objects.requireNonNull(correlationHeader, "correlationHeader");
Objects.requireNonNull(responsePacket, "responsePacket");
WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
return new WebResourceResponsePacket(
header,
responsePacket.getStatusCode(),
responsePacket.getContentType(),
responsePacket.getHeaders(),
responsePacket.getBody(),
null
);
}
/**
* Maps a v1.0.0 {@link WebStreamStartPacket_v1_0_0_B} to a v1.0.1 {@link WebStreamStartPacket_v1_0_1_B}
* using the provided synthetic correlation header.
*
* @param correlationHeader synthetic correlation header
* @param streamPacket 1.0.0 stream start
* @return v1.0.1 stream start
*/
public static WebStreamStartPacket_v1_0_1_B toV101StreamStart(WebPacketHeader correlationHeader, WebStreamStartPacket_v1_0_0_B streamPacket) {
Objects.requireNonNull(correlationHeader, "correlationHeader");
Objects.requireNonNull(streamPacket, "streamPacket");
WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
return new WebStreamStartPacket_v1_0_1_B(
header,
streamPacket.getStatusCode(),
streamPacket.getContentType(),
streamPacket.getHeaders(),
streamPacket.getTotalLength()
);
}
/**
* Maps a v1.0.0 {@link WebStreamChunkPacket_v1_0_0_B} to a v1.0.1 {@link WebStreamChunkPacket_v1_0_1_B}
* using the provided synthetic correlation header.
*
* @param correlationHeader synthetic correlation header
* @param streamPacket 1.0.0 stream chunk
* @return v1.0.1 stream chunk
*/
public static WebStreamChunkPacket_v1_0_1_B toV101StreamChunk(WebPacketHeader correlationHeader, WebStreamChunkPacket_v1_0_0_B streamPacket) {
Objects.requireNonNull(correlationHeader, "correlationHeader");
Objects.requireNonNull(streamPacket, "streamPacket");
WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
return new WebStreamChunkPacket_v1_0_1_B(
header,
streamPacket.getSeq(),
streamPacket.getData()
);
}
/**
* Maps a v1.0.0 {@link WebStreamEndPacket_v1_0_0_B} to a v1.0.1 {@link WebStreamEndPacket_v1_0_1_B}
* using the provided synthetic correlation header.
*
* @param correlationHeader synthetic correlation header
* @param streamPacket v1.0.0 stream end packet
* @return v1.0.1 stream end packet
*/
public static WebStreamEndPacket_v1_0_1_B toV101StreamEnd(
WebPacketHeader correlationHeader,
WebStreamEndPacket_v1_0_0_B streamPacket
) {
Objects.requireNonNull(correlationHeader, "correlationHeader");
Objects.requireNonNull(streamPacket, "streamPacket");
WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
// v1.0.0 has no error string -> null
return new WebStreamEndPacket_v1_0_1_B(
header,
streamPacket.isOk(),
null
);
}
/**
* Creates a header that mirrors correlation fields from the provided header and updates timestamp/flags.
*
* @param correlationHeader correlation header to mirror
* @param extraFlags flags to OR
* @return mirrored header
*/
public static WebPacketHeader mirrorCorrelation(WebPacketHeader correlationHeader, int extraFlags) {
Objects.requireNonNull(correlationHeader, "correlationHeader");
return new WebPacketHeader(
correlationHeader.getRequestId(),
correlationHeader.getTabId(),
correlationHeader.getPageId(),
correlationHeader.getFrameId(),
correlationHeader.getFlags() | extraFlags,
System.currentTimeMillis()
);
}
/**
* Maps 1.0.0 {@link WebRequestMethod}.
*
* @param method 1.0.0 method
* @return method string
*/
public static String mapMethod(WebRequestMethod method) {
if (method == null) return "GET";
return switch (method) {
case GET -> "GET";
case POST, EXECUTE, SCRIPT -> "POST";
};
}
/**
* 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;
};
}
}