Add more protocol stuff

This commit is contained in:
MrLetsplay 2023-12-04 21:37:32 +01:00
parent 72b4e547fb
commit e802d50863
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
12 changed files with 149 additions and 15 deletions

View File

@ -2,4 +2,4 @@ package me.mrletsplay.shareclientcore.connection;
import me.mrletsplay.shareclientcore.document.Char; import me.mrletsplay.shareclientcore.document.Char;
public record Change(int document, ChangeType type, Char character) {} public record Change(String documentPath, ChangeType type, Char character) {}

View File

@ -9,6 +9,11 @@ public class DummyConnection implements RemoteConnection {
} }
@Override
public void disconnect() {
}
@Override @Override
public int getSiteID() { public int getSiteID() {
return 0; return 0;

View File

@ -11,6 +11,8 @@ public interface RemoteConnection {
public void connect(String sessionID) throws ConnectionException; public void connect(String sessionID) throws ConnectionException;
public void disconnect();
public int getSiteID(); public int getSiteID();
public void send(Message message) throws ConnectionException; public void send(Message message) throws ConnectionException;

View File

@ -11,6 +11,7 @@ import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.java_websocket.client.WebSocketClient; import org.java_websocket.client.WebSocketClient;
import org.java_websocket.framing.CloseFrame;
import org.java_websocket.handshake.ServerHandshake; import org.java_websocket.handshake.ServerHandshake;
import me.mrletsplay.shareclientcore.connection.message.ClientHelloMessage; import me.mrletsplay.shareclientcore.connection.message.ClientHelloMessage;
@ -43,14 +44,22 @@ public class WebSocketConnection implements RemoteConnection {
try { try {
if(!client.connectBlocking(30, TimeUnit.SECONDS)) throw new IOException("Failed to connect to WebSocket server"); if(!client.connectBlocking(30, TimeUnit.SECONDS)) throw new IOException("Failed to connect to WebSocket server");
send(new ClientHelloMessage(username, sessionID)); send(new ClientHelloMessage(username, sessionID));
wait.wait(30_000L); synchronized(wait) { wait.wait(30_000L); }
if(!helloReceived) throw new ConnectionException("Server did not send hello"); if(!helloReceived) {
client.close();
throw new ConnectionException("Server did not send hello");
}
if(connectException != null) throw connectException; if(connectException != null) throw connectException;
} catch (InterruptedException | IOException e) { } catch (InterruptedException | IOException e) {
throw new ConnectionException("Failed to establish connection", e); throw new ConnectionException("Failed to establish connection", e);
} }
} }
@Override
public void disconnect() {
client.close(CloseFrame.NORMAL);
}
@Override @Override
public int getSiteID() { public int getSiteID() {
return siteID; return siteID;
@ -120,7 +129,7 @@ public class WebSocketConnection implements RemoteConnection {
close(); close();
} }
wait.notifyAll(); synchronized(wait) { wait.notifyAll(); }
return; return;
} }

View File

@ -0,0 +1,7 @@
package me.mrletsplay.shareclientcore.connection.message;
public interface AddressableMessage extends Message {
public int siteID();
}

View File

@ -17,14 +17,14 @@ public record ChangeMessage(Change change) implements Message {
@Override @Override
public void serialize(DataOutputStream out) throws IOException { public void serialize(DataOutputStream out) throws IOException {
out.writeInt(change.document()); out.writeUTF(change.documentPath());
out.writeUTF(change.type().name()); out.writeUTF(change.type().name());
change.character().serialize(out); change.character().serialize(out);
} }
public static ChangeMessage deserialize(DataInputStream in) throws IOException { public static ChangeMessage deserialize(DataInputStream in) throws IOException {
try { 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) { }catch(IllegalArgumentException e) {
throw new IOException("Invalid change type", e); throw new IOException("Invalid change type", e);
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -16,12 +16,12 @@ public class SharedDocument implements MessageListener {
private RemoteConnection connection; private RemoteConnection connection;
private CharBag charBag; private CharBag charBag;
private int document; private String path;
private int site; private int site;
private int lamport; private int lamport;
private Set<DocumentListener> listeners; private Set<DocumentListener> listeners;
public SharedDocument(RemoteConnection connection) { public SharedDocument(RemoteConnection connection, String path) {
this.connection = connection; this.connection = connection;
connection.addListener(this); connection.addListener(this);
@ -29,7 +29,7 @@ public class SharedDocument implements MessageListener {
charBag.add(Char.START_OF_DOCUMENT); charBag.add(Char.START_OF_DOCUMENT);
charBag.add(Char.END_OF_DOCUMENT); charBag.add(Char.END_OF_DOCUMENT);
this.document = 0; // TODO: implement this.path = path;
this.site = connection.getSiteID(); this.site = connection.getSiteID();
this.listeners = new HashSet<>(); this.listeners = new HashSet<>();
} }
@ -52,7 +52,7 @@ public class SharedDocument implements MessageListener {
lamport++; lamport++;
Char ch = new Char(newPos, lamport, chars[i]); Char ch = new Char(newPos, lamport, chars[i]);
charBag.add(ch); charBag.add(ch);
changes[i] = new Change(document, ChangeType.ADD, ch); changes[i] = new Change(path, ChangeType.ADD, ch);
charBefore = ch; charBefore = ch;
} }
@ -78,7 +78,7 @@ public class SharedDocument implements MessageListener {
while(n-- > 0) { while(n-- > 0) {
// TODO: more efficient implementation (e.g. range delete in CharBag) // TODO: more efficient implementation (e.g. range delete in CharBag)
Char toRemove = charBag.get(index + 1); 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); charBag.remove(toRemove);
} }
@ -139,6 +139,8 @@ public class SharedDocument implements MessageListener {
public void onMessage(Message message) { public void onMessage(Message message) {
if(message instanceof ChangeMessage change) { if(message instanceof ChangeMessage change) {
Change c = change.change(); Change c = change.change();
if(!c.documentPath().equals(path)) return;
System.out.println("Change: " + c + " | " + Arrays.toString(c.character().position())); System.out.println("Change: " + c + " | " + Arrays.toString(c.character().position()));
switch(c.type()) { switch(c.type()) {
case ADD -> remoteInsert(c.character()); case ADD -> remoteInsert(c.character());

View File

@ -12,7 +12,7 @@ public class DocumentTest {
@Test @Test
public void testLocalInsert() { public void testLocalInsert() {
SharedDocument doc = new SharedDocument(new DummyConnection()); SharedDocument doc = new SharedDocument(new DummyConnection(), "test");
doc.localInsert(0, "Hello"); doc.localInsert(0, "Hello");
assertEquals("Hello", doc.getContents()); assertEquals("Hello", doc.getContents());
doc.localInsert(5, " World"); doc.localInsert(5, " World");
@ -23,7 +23,7 @@ public class DocumentTest {
@Test @Test
public void testLocalInsertInvalidIndexFails() { public void testLocalInsertInvalidIndexFails() {
SharedDocument doc = new SharedDocument(new DummyConnection()); SharedDocument doc = new SharedDocument(new DummyConnection(), "test");
doc.localInsert(0, "Hello"); doc.localInsert(0, "Hello");
assertThrows(IllegalArgumentException.class, () -> doc.localInsert(-1, "Test")); assertThrows(IllegalArgumentException.class, () -> doc.localInsert(-1, "Test"));
assertThrows(IllegalArgumentException.class, () -> doc.localInsert(6, "Test")); assertThrows(IllegalArgumentException.class, () -> doc.localInsert(6, "Test"));
@ -31,7 +31,7 @@ public class DocumentTest {
@Test @Test
public void testLocalDelete() { public void testLocalDelete() {
SharedDocument doc = new SharedDocument(new DummyConnection()); SharedDocument doc = new SharedDocument(new DummyConnection(), "test");
doc.localInsert(0, "Hello World!"); doc.localInsert(0, "Hello World!");
doc.localDelete(5, 6); doc.localDelete(5, 6);
assertEquals("Hello!", doc.getContents()); assertEquals("Hello!", doc.getContents());
@ -39,7 +39,7 @@ public class DocumentTest {
@Test @Test
public void testLocalDeleteInvalidIndexFails() { public void testLocalDeleteInvalidIndexFails() {
SharedDocument doc = new SharedDocument(new DummyConnection()); SharedDocument doc = new SharedDocument(new DummyConnection(), "test");
doc.localInsert(0, "Hello World!"); doc.localInsert(0, "Hello World!");
assertThrows(IllegalArgumentException.class, () -> doc.localDelete(-1, 10)); assertThrows(IllegalArgumentException.class, () -> doc.localDelete(-1, 10));
assertThrows(IllegalArgumentException.class, () -> doc.localDelete(12, 1)); assertThrows(IllegalArgumentException.class, () -> doc.localDelete(12, 1));