Updated to latest Protocol Version

This commit is contained in:
UnlegitDqrk
2026-02-22 18:20:57 +01:00
parent 69be55cac0
commit 4376fe6daa
7 changed files with 517 additions and 63 deletions

View File

@@ -1,90 +1,98 @@
package org.openautonomousconnection.webclient;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import org.openautonomousconnection.infonamelib.LibClientImpl;
import org.openautonomousconnection.infonamelib.OacWebUrlInstaller;
import lombok.Getter;
import org.openautonomousconnection.oacswing.component.OACOptionPane;
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolINSServerEvent;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.LibClientImpl_v1_0_1_B;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.WebFlagInspector;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.WebRequestContextProvider;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import org.openautonomousconnection.webclient.ui.BrowserTab;
import java.awt.*;
import java.awt.Component;
import java.io.ByteArrayOutputStream;
import java.net.URL;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Protocol client implementation for the WebClient.
* WebClient Protocol implementation (v1.0.1).
*
* <p>Implements full stream assembly with strict correlation via:
* requestId + tabId + pageId + frameId.</p>
*/
public class ClientImpl extends ProtocolClient {
public final class ClientImpl extends ProtocolClient {
private static final long MAX_STREAM_BYTES = 64L * 1024L * 1024L; // 64MB safety cap
private static final int MAX_CONCURRENT_STREAMS = 256;
@Getter
private final LibImpl libImpl = new LibImpl();
private final AtomicBoolean connectedInitialized = new AtomicBoolean(false);
private final AtomicBoolean serverConnectionInitialized = new AtomicBoolean(false);
private final Component dialogParent;
private final Runnable onServerReady;
public ClientImpl(Component dialogParent, Runnable onServerReady) {
this.dialogParent = dialogParent;
this.onServerReady = Objects.requireNonNull(onServerReady, "onServerReady");
this.onServerReady = Objects.requireNonNull(onServerReady);
}
@Override
public boolean trustINS(String caFingerprint) {
Object[] options = {"Continue", "Cancel"};
int result = OACOptionPane.showOptionDialog(
return OACOptionPane.showOptionDialog(
dialogParent,
"You never connected to this INS before!\n" +
"Fingerprint: " + caFingerprint + "\nDo you want to connect?",
"Fingerprint: " + caFingerprint + "\nContinue?",
"INS Connection",
OACOptionPane.YES_NO_OPTION,
OACOptionPane.INFORMATION_MESSAGE,
null,
options,
options[0]
);
return result == 0;
) == 0;
}
@Override
public boolean trustNewINSFingerprint(String oldCAFingerprint, String newCAFingerprint) {
Object[] options = {"Continue", "Cancel"};
int result = OACOptionPane.showOptionDialog(
return OACOptionPane.showOptionDialog(
dialogParent,
"The fingerprint does not match with the saved fingerprint!\n" +
"Saved Fingerprint: " + oldCAFingerprint + "\n" +
"New Fingerprint: " + newCAFingerprint + "\n" +
"Do you want to connect?",
"Saved: " + oldCAFingerprint + "\nNew: " + newCAFingerprint + "\nContinue?",
"INS Connection",
OACOptionPane.YES_NO_OPTION,
OACOptionPane.INFORMATION_MESSAGE,
null,
options,
options[0]
);
return result == 0;
) == 0;
}
@Listener
public void onConnected(ConnectedToProtocolINSServerEvent event) {
try {
buildServerConnection(null, getProtocolBridge().getProtocolValues().ssl);
OacWebUrlInstaller.installOnce(getProtocolBridge().getProtocolValues().eventManager, this, libImpl);
if (connectedInitialized.compareAndSet(false, true)) {
if (serverConnectionInitialized.compareAndSet(false, true)) {
buildServerConnection(null, getProtocolBridge().getProtocolValues().ssl);
onServerReady.run();
}
} catch (Exception e) {
getProtocolBridge().getLogger().exception("Failed to build Server connection", e);
OACOptionPane.showMessageDialog(
dialogParent,
"Failed to to build Server connection:\n" + e.getMessage(),
"Server Connection",
OACOptionPane.ERROR_MESSAGE
);
serverConnectionInitialized.set(false);
throw new RuntimeException(e);
}
}
private class LibImpl extends LibClientImpl {
public final class LibImpl extends LibClientImpl_v1_0_1_B {
private final WebRequestContextProvider provider = new WebRequestContextProvider.Default();
private final WebFlagInspector inspector = new WebFlagInspector.Default();
private final ConcurrentHashMap<StreamKey, StreamState> streams = new ConcurrentHashMap<>();
private BrowserTab currentTab;
@Override
public void serverConnectionFailed(Exception exception) {
getProtocolBridge().getLogger().exception("Failed to connect to server", exception);
@@ -95,5 +103,141 @@ public class ClientImpl extends ProtocolClient {
OACOptionPane.ERROR_MESSAGE
);
}
public void bindTab(BrowserTab tab) {
this.currentTab = tab;
}
@Override
public boolean isStream(WebPacketHeader header) {
return inspector.isStream(header);
}
@Override
public WebRequestContext contextFor(URL url) {
return provider.contextFor(url);
}
@Override
public void streamStart(WebPacketHeader header,
int statusCode,
String contentType,
Map<String, String> headers,
long totalLength) {
if (streams.size() >= MAX_CONCURRENT_STREAMS) {
throw new IllegalStateException("Too many concurrent streams");
}
StreamKey key = new StreamKey(header);
streams.put(key, new StreamState(statusCode, contentType, headers, totalLength));
}
@Override
public void streamChunk(WebPacketHeader header, int seq, byte[] data) {
StreamState state = streams.get(new StreamKey(header));
if (state == null) {
throw new IllegalStateException("Chunk without streamStart");
}
state.append(seq, data);
}
@Override
public void streamEnd(WebPacketHeader header, boolean ok, String error) {
StreamState state = streams.get(new StreamKey(header));
if (state != null) {
state.markEnd(ok, error);
}
}
@Override
public void streamFinish(WebPacketHeader header, byte[] ignored) {
StreamKey key = new StreamKey(header);
StreamState state = streams.remove(key);
if (state == null) return;
byte[] content = state.finish();
if (currentTab != null) {
currentTab.handleStreamFinished(
header.getRequestId(),
header.getTabId(),
header.getPageId(),
header.getFrameId(),
state.statusCode,
state.contentType,
state.headers,
content
);
}
}
}
}
private record StreamKey(long requestId, long tabId, long pageId, long frameId) {
StreamKey(WebPacketHeader h) {
this(h.getRequestId(), h.getTabId(), h.getPageId(), h.getFrameId());
}
}
private static final class StreamState {
private final int statusCode;
private final String contentType;
private final Map<String, String> headers;
private final long declaredLength;
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
private int expectedSeq = 0;
private long written = 0;
private boolean ended = false;
private boolean ok = true;
StreamState(int statusCode,
String contentType,
Map<String, String> headers,
long declaredLength) {
this.statusCode = statusCode;
this.contentType = contentType == null ? "application/octet-stream" : contentType;
this.headers = headers == null ? Map.of() : Map.copyOf(headers);
this.declaredLength = declaredLength;
}
void append(int seq, byte[] data) {
if (ended) throw new IllegalStateException("Chunk after end");
if (seq != expectedSeq) throw new IllegalStateException("Out-of-order chunk");
expectedSeq++;
if (data == null || data.length == 0) return;
written += data.length;
if (written > MAX_STREAM_BYTES)
throw new IllegalStateException("Stream exceeds limit");
buffer.writeBytes(data);
}
void markEnd(boolean ok, String error) {
this.ended = true;
this.ok = ok;
}
byte[] finish() {
if (!ok) return new byte[0];
byte[] data = buffer.toByteArray();
if (declaredLength > 0 && data.length != declaredLength) {
// tolerated but can log if needed
}
return data;
}
}
}