From e802d50863e9b88862971fe7c1b2bfe40e5e973d Mon Sep 17 00:00:00 2001 From: MrLetsplay Date: Mon, 4 Dec 2023 21:37:32 +0100 Subject: [PATCH] Add more protocol stuff --- .../shareclientcore/connection/Change.java | 2 +- .../connection/DummyConnection.java | 5 ++++ .../connection/RemoteConnection.java | 2 ++ .../connection/WebSocketConnection.java | 15 ++++++++-- .../message/AddressableMessage.java | 7 +++++ .../connection/message/ChangeMessage.java | 4 +-- .../connection/message/ChecksumMessage.java | 30 +++++++++++++++++++ .../connection/message/FullSyncMessage.java | 30 +++++++++++++++++++ .../message/RequestChecksumMessage.java | 24 +++++++++++++++ .../message/RequestFullSyncMessage.java | 25 ++++++++++++++++ .../document/SharedDocument.java | 12 ++++---- .../shareclientcore/DocumentTest.java | 8 ++--- 12 files changed, 149 insertions(+), 15 deletions(-) create mode 100644 src/main/java/me/mrletsplay/shareclientcore/connection/message/AddressableMessage.java create mode 100644 src/main/java/me/mrletsplay/shareclientcore/connection/message/ChecksumMessage.java create mode 100644 src/main/java/me/mrletsplay/shareclientcore/connection/message/FullSyncMessage.java create mode 100644 src/main/java/me/mrletsplay/shareclientcore/connection/message/RequestChecksumMessage.java create mode 100644 src/main/java/me/mrletsplay/shareclientcore/connection/message/RequestFullSyncMessage.java diff --git a/src/main/java/me/mrletsplay/shareclientcore/connection/Change.java b/src/main/java/me/mrletsplay/shareclientcore/connection/Change.java index c3eef74..7b65d83 100644 --- a/src/main/java/me/mrletsplay/shareclientcore/connection/Change.java +++ b/src/main/java/me/mrletsplay/shareclientcore/connection/Change.java @@ -2,4 +2,4 @@ package me.mrletsplay.shareclientcore.connection; import me.mrletsplay.shareclientcore.document.Char; -public record Change(int document, ChangeType type, Char character) {} +public record Change(String documentPath, ChangeType type, Char character) {} diff --git a/src/main/java/me/mrletsplay/shareclientcore/connection/DummyConnection.java b/src/main/java/me/mrletsplay/shareclientcore/connection/DummyConnection.java index e930e22..bb44f3e 100644 --- a/src/main/java/me/mrletsplay/shareclientcore/connection/DummyConnection.java +++ b/src/main/java/me/mrletsplay/shareclientcore/connection/DummyConnection.java @@ -9,6 +9,11 @@ public class DummyConnection implements RemoteConnection { } + @Override + public void disconnect() { + + } + @Override public int getSiteID() { return 0; diff --git a/src/main/java/me/mrletsplay/shareclientcore/connection/RemoteConnection.java b/src/main/java/me/mrletsplay/shareclientcore/connection/RemoteConnection.java index 1993934..7005e0e 100644 --- a/src/main/java/me/mrletsplay/shareclientcore/connection/RemoteConnection.java +++ b/src/main/java/me/mrletsplay/shareclientcore/connection/RemoteConnection.java @@ -11,6 +11,8 @@ public interface RemoteConnection { public void connect(String sessionID) throws ConnectionException; + public void disconnect(); + public int getSiteID(); public void send(Message message) throws ConnectionException; diff --git a/src/main/java/me/mrletsplay/shareclientcore/connection/WebSocketConnection.java b/src/main/java/me/mrletsplay/shareclientcore/connection/WebSocketConnection.java index 90dee4f..34b9570 100644 --- a/src/main/java/me/mrletsplay/shareclientcore/connection/WebSocketConnection.java +++ b/src/main/java/me/mrletsplay/shareclientcore/connection/WebSocketConnection.java @@ -11,6 +11,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import org.java_websocket.client.WebSocketClient; +import org.java_websocket.framing.CloseFrame; import org.java_websocket.handshake.ServerHandshake; import me.mrletsplay.shareclientcore.connection.message.ClientHelloMessage; @@ -43,14 +44,22 @@ public class WebSocketConnection implements RemoteConnection { try { if(!client.connectBlocking(30, TimeUnit.SECONDS)) throw new IOException("Failed to connect to WebSocket server"); send(new ClientHelloMessage(username, sessionID)); - wait.wait(30_000L); - if(!helloReceived) throw new ConnectionException("Server did not send hello"); + synchronized(wait) { wait.wait(30_000L); } + if(!helloReceived) { + client.close(); + throw new ConnectionException("Server did not send hello"); + } if(connectException != null) throw connectException; } catch (InterruptedException | IOException e) { throw new ConnectionException("Failed to establish connection", e); } } + @Override + public void disconnect() { + client.close(CloseFrame.NORMAL); + } + @Override public int getSiteID() { return siteID; @@ -120,7 +129,7 @@ public class WebSocketConnection implements RemoteConnection { close(); } - wait.notifyAll(); + synchronized(wait) { wait.notifyAll(); } return; } diff --git a/src/main/java/me/mrletsplay/shareclientcore/connection/message/AddressableMessage.java b/src/main/java/me/mrletsplay/shareclientcore/connection/message/AddressableMessage.java new file mode 100644 index 0000000..1cf1b18 --- /dev/null +++ b/src/main/java/me/mrletsplay/shareclientcore/connection/message/AddressableMessage.java @@ -0,0 +1,7 @@ +package me.mrletsplay.shareclientcore.connection.message; + +public interface AddressableMessage extends Message { + + public int siteID(); + +} diff --git a/src/main/java/me/mrletsplay/shareclientcore/connection/message/ChangeMessage.java b/src/main/java/me/mrletsplay/shareclientcore/connection/message/ChangeMessage.java index 8038952..99b1155 100644 --- a/src/main/java/me/mrletsplay/shareclientcore/connection/message/ChangeMessage.java +++ b/src/main/java/me/mrletsplay/shareclientcore/connection/message/ChangeMessage.java @@ -17,14 +17,14 @@ public record ChangeMessage(Change change) implements Message { @Override public void serialize(DataOutputStream out) throws IOException { - out.writeInt(change.document()); + out.writeUTF(change.documentPath()); out.writeUTF(change.type().name()); change.character().serialize(out); } public static ChangeMessage deserialize(DataInputStream in) throws IOException { try { - return new ChangeMessage(new Change(in.readInt(), ChangeType.valueOf(in.readUTF()), Char.deserialize(in))); + return new ChangeMessage(new Change(in.readUTF(), ChangeType.valueOf(in.readUTF()), Char.deserialize(in))); }catch(IllegalArgumentException e) { throw new IOException("Invalid change type", e); } diff --git a/src/main/java/me/mrletsplay/shareclientcore/connection/message/ChecksumMessage.java b/src/main/java/me/mrletsplay/shareclientcore/connection/message/ChecksumMessage.java new file mode 100644 index 0000000..fa246af --- /dev/null +++ b/src/main/java/me/mrletsplay/shareclientcore/connection/message/ChecksumMessage.java @@ -0,0 +1,30 @@ +package me.mrletsplay.shareclientcore.connection.message; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public record ChecksumMessage(int siteID, String documentPath, byte[] checksum) implements AddressableMessage { + + @Override + public MessageType getType() { + return MessageType.CHECKSUM; + } + + @Override + public void serialize(DataOutputStream out) throws IOException { + out.writeInt(siteID); + out.writeUTF(documentPath); + out.writeInt(checksum.length); + out.write(checksum); + } + + public static ChecksumMessage deserialize(DataInputStream in) throws IOException { + try { + return new ChecksumMessage(in.readInt(), in.readUTF(), in.readNBytes(in.readInt())); + }catch(IllegalArgumentException e) { + throw new IOException("Invalid checksum length", e); + } + } + +} diff --git a/src/main/java/me/mrletsplay/shareclientcore/connection/message/FullSyncMessage.java b/src/main/java/me/mrletsplay/shareclientcore/connection/message/FullSyncMessage.java new file mode 100644 index 0000000..6829992 --- /dev/null +++ b/src/main/java/me/mrletsplay/shareclientcore/connection/message/FullSyncMessage.java @@ -0,0 +1,30 @@ +package me.mrletsplay.shareclientcore.connection.message; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public record FullSyncMessage(int siteID, String documentPath, byte[] content) implements AddressableMessage { + + @Override + public MessageType getType() { + return MessageType.FULL_SYNC; + } + + @Override + public void serialize(DataOutputStream out) throws IOException { + out.writeInt(siteID); + out.writeUTF(documentPath); + out.writeInt(content.length); + out.write(content); + } + + public static FullSyncMessage deserialize(DataInputStream in) throws IOException { + try { + return new FullSyncMessage(in.readInt(), in.readUTF(), in.readNBytes(in.readInt())); + }catch(IllegalArgumentException e) { + throw new IOException("Invalid content length", e); + } + } + +} diff --git a/src/main/java/me/mrletsplay/shareclientcore/connection/message/RequestChecksumMessage.java b/src/main/java/me/mrletsplay/shareclientcore/connection/message/RequestChecksumMessage.java new file mode 100644 index 0000000..250a1d3 --- /dev/null +++ b/src/main/java/me/mrletsplay/shareclientcore/connection/message/RequestChecksumMessage.java @@ -0,0 +1,24 @@ +package me.mrletsplay.shareclientcore.connection.message; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public record RequestChecksumMessage(int siteID, String documentPath) implements AddressableMessage { + + @Override + public MessageType getType() { + return MessageType.REQUEST_CHECKSUM; + } + + @Override + public void serialize(DataOutputStream out) throws IOException { + out.writeInt(siteID); + out.writeUTF(documentPath); + } + + public static RequestChecksumMessage deserialize(DataInputStream in) throws IOException { + return new RequestChecksumMessage(in.readInt(), in.readUTF()); + } + +} diff --git a/src/main/java/me/mrletsplay/shareclientcore/connection/message/RequestFullSyncMessage.java b/src/main/java/me/mrletsplay/shareclientcore/connection/message/RequestFullSyncMessage.java new file mode 100644 index 0000000..a606da7 --- /dev/null +++ b/src/main/java/me/mrletsplay/shareclientcore/connection/message/RequestFullSyncMessage.java @@ -0,0 +1,25 @@ +package me.mrletsplay.shareclientcore.connection.message; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public record RequestFullSyncMessage(int siteID, String documentPath) implements AddressableMessage { + + @Override + public MessageType getType() { + return MessageType.REQUEST_FULL_SYNC; + } + + @Override + public void serialize(DataOutputStream out) throws IOException { + out.writeInt(siteID); + out.writeBoolean(documentPath != null); + if(documentPath != null) out.writeUTF(documentPath); + } + + public static RequestFullSyncMessage deserialize(DataInputStream in) throws IOException { + return new RequestFullSyncMessage(in.readInt(), in.readBoolean() ? in.readUTF() : null); + } + +} diff --git a/src/main/java/me/mrletsplay/shareclientcore/document/SharedDocument.java b/src/main/java/me/mrletsplay/shareclientcore/document/SharedDocument.java index 921bfdc..5e3f4bb 100644 --- a/src/main/java/me/mrletsplay/shareclientcore/document/SharedDocument.java +++ b/src/main/java/me/mrletsplay/shareclientcore/document/SharedDocument.java @@ -16,12 +16,12 @@ public class SharedDocument implements MessageListener { private RemoteConnection connection; private CharBag charBag; - private int document; + private String path; private int site; private int lamport; private Set listeners; - public SharedDocument(RemoteConnection connection) { + public SharedDocument(RemoteConnection connection, String path) { this.connection = connection; connection.addListener(this); @@ -29,7 +29,7 @@ public class SharedDocument implements MessageListener { charBag.add(Char.START_OF_DOCUMENT); charBag.add(Char.END_OF_DOCUMENT); - this.document = 0; // TODO: implement + this.path = path; this.site = connection.getSiteID(); this.listeners = new HashSet<>(); } @@ -52,7 +52,7 @@ public class SharedDocument implements MessageListener { lamport++; Char ch = new Char(newPos, lamport, chars[i]); charBag.add(ch); - changes[i] = new Change(document, ChangeType.ADD, ch); + changes[i] = new Change(path, ChangeType.ADD, ch); charBefore = ch; } @@ -78,7 +78,7 @@ public class SharedDocument implements MessageListener { while(n-- > 0) { // TODO: more efficient implementation (e.g. range delete in CharBag) Char toRemove = charBag.get(index + 1); - changes[n] = new Change(document, ChangeType.REMOVE, toRemove); + changes[n] = new Change(path, ChangeType.REMOVE, toRemove); charBag.remove(toRemove); } @@ -139,6 +139,8 @@ public class SharedDocument implements MessageListener { public void onMessage(Message message) { if(message instanceof ChangeMessage change) { Change c = change.change(); + if(!c.documentPath().equals(path)) return; + System.out.println("Change: " + c + " | " + Arrays.toString(c.character().position())); switch(c.type()) { case ADD -> remoteInsert(c.character()); diff --git a/src/test/java/me/mrletsplay/shareclientcore/DocumentTest.java b/src/test/java/me/mrletsplay/shareclientcore/DocumentTest.java index 6e35723..5b534b8 100644 --- a/src/test/java/me/mrletsplay/shareclientcore/DocumentTest.java +++ b/src/test/java/me/mrletsplay/shareclientcore/DocumentTest.java @@ -12,7 +12,7 @@ public class DocumentTest { @Test public void testLocalInsert() { - SharedDocument doc = new SharedDocument(new DummyConnection()); + SharedDocument doc = new SharedDocument(new DummyConnection(), "test"); doc.localInsert(0, "Hello"); assertEquals("Hello", doc.getContents()); doc.localInsert(5, " World"); @@ -23,7 +23,7 @@ public class DocumentTest { @Test public void testLocalInsertInvalidIndexFails() { - SharedDocument doc = new SharedDocument(new DummyConnection()); + SharedDocument doc = new SharedDocument(new DummyConnection(), "test"); doc.localInsert(0, "Hello"); assertThrows(IllegalArgumentException.class, () -> doc.localInsert(-1, "Test")); assertThrows(IllegalArgumentException.class, () -> doc.localInsert(6, "Test")); @@ -31,7 +31,7 @@ public class DocumentTest { @Test public void testLocalDelete() { - SharedDocument doc = new SharedDocument(new DummyConnection()); + SharedDocument doc = new SharedDocument(new DummyConnection(), "test"); doc.localInsert(0, "Hello World!"); doc.localDelete(5, 6); assertEquals("Hello!", doc.getContents()); @@ -39,7 +39,7 @@ public class DocumentTest { @Test public void testLocalDeleteInvalidIndexFails() { - SharedDocument doc = new SharedDocument(new DummyConnection()); + SharedDocument doc = new SharedDocument(new DummyConnection(), "test"); doc.localInsert(0, "Hello World!"); assertThrows(IllegalArgumentException.class, () -> doc.localDelete(-1, 10)); assertThrows(IllegalArgumentException.class, () -> doc.localDelete(12, 1));