From 0f47403d0220757fed189b76e2071e25b1025cb8 Mon Sep 17 00:00:00 2001 From: Runemoro Date: Wed, 3 Jun 2020 13:39:42 -0400 Subject: Split GUI code to separate module (#242) * Split into modules * Post merge compile fixes Co-authored-by: modmuss50 --- enigma-server/docs/protocol.md | 366 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 enigma-server/docs/protocol.md (limited to 'enigma-server/docs/protocol.md') diff --git a/enigma-server/docs/protocol.md b/enigma-server/docs/protocol.md new file mode 100644 index 00000000..c14ecb81 --- /dev/null +++ b/enigma-server/docs/protocol.md @@ -0,0 +1,366 @@ +# Enigma protocol +Enigma uses TCP sockets for communication. Data is sent in each direction as a continuous stream, with packets being +concatenated one after the other. + +In this document, data will be represented in C-like pseudocode. The primitive data types will be the same as those +defined by Java's [DataOutputStream](https://docs.oracle.com/javase/7/docs/api/java/io/DataOutputStream.html), i.e. in +big-endian order for multi-byte integers (`short`, `int` and `long`). The one exception is for Strings, which do *not* +use the same modified UTF format as in `DataOutputStream`, I repeat, the normal `writeUTF` method in `DataOutputStream` +(and the corresponding method in `DataInputStream`) should *not* be used. Instead, there is a custom `utf` struct for +Strings, see below. + +## Login protocol +``` +Client Server +| | +| Login | +| >>>>>>>>>>>>> | +| | +| SyncMappings | +| <<<<<<<<<<<<< | +| | +| ConfirmChange | +| >>>>>>>>>>>>> | +``` +1. On connect, the client sends a login packet to the server. This allows the server to test the validity of the client, + as well as allowing the client to declare metadata about itself, such as the username. +1. After validating the login packet, the server sends all its mappings to the client, and the client will apply them. +1. Upon receiving the mappings, the client sends a `ConfirmChange` packet with `sync_id` set to 0, to confirm that it + has received the mappings and is in sync with the server. Once the server receives this packet, the client will be + allowed to modify mappings. + +The server will not accept any other packets from the client until this entire exchange has been completed. + +## Kicking clients +When the server kicks a client, it may optionally send a `Kick` packet immediately before closing the connection, which +contains the reason why the client was kicked (so the client can display it to the user). This is not required though - +the server may simply terminate the connection. + +## Changing mappings +This section uses the example of renaming, but the same pattern applies to all mapping changes. +``` +Client A Server Client B +| | | +| RenameC2S | | +| >>>>>>>>> | | +| | | +| | RenameS2C | +| | >>>>>>>>>>>>> | +| | | +| | ConfirmChange | +| | <<<<<<<<<<<<< | +``` + +1. Client A validates the name and updates the mapping client-side to give the impression there is no latency >:) +1. Client A sends a rename packet to the server, notifying it of the rename. +1. The server assesses the validity of the rename. If it is invalid for whatever reason (e.g. the mapping was locked or + the name contains invalid characters), then the server sends an appropriate packet back to client A to revert the + change, with `sync_id` set to 0. The server will ignore any `ConfirmChange` packets it receives in response to this. +1. If the rename was valid, the server will lock all clients except client A from being able to modify this mapping, and + then send an appropriate packet to all clients except client A notifying them of this rename. The `sync_id` will be a + unique non-zero value identifying this change. +1. Each client responds to this packet by updating their mappings locally to reflect this change, then sending a + `ConfirmChange` packet with the same `sync_id` as the one in the packet they received, to confirm that they have + received the change. +1. When the server receives the `ConfirmChange` packet, and another change to that mapping hasn't occurred since, the + server will unlock that mapping for that client and allow them to make changes again. + +## Packets +```c +struct Packet { + unsigned short packet_id; + data[]; // depends on packet_id +} +``` +The IDs for client-to-server packets are as follows: +- 0: `Login` +- 1: `ConfirmChange` +- 2: `Rename` +- 3: `RemoveMapping` +- 4: `ChangeDocs` +- 5: `MarkDeobfuscated` +- 6: `Message` + +The IDs for server-to-client packets are as follows: +- 0: `Kick` +- 1: `SyncMappings` +- 2: `Rename` +- 3: `RemoveMapping` +- 4: `ChangeDocs` +- 5: `MarkDeobfuscated` +- 6: `Message` +- 7: `UserList` + +### The utf struct +```c +struct utf { + unsigned short length; + byte data[length]; +} +``` +- `length`: The number of bytes in the UTF-8 encoding of the string. Note, this may not be the same as the number of + Unicode characters in the string. +- `data`: A standard UTF-8 encoded byte array representing the string. + +### The Entry struct +```c +enum EntryType { + ENTRY_CLASS = 0, ENTRY_FIELD = 1, ENTRY_METHOD = 2, ENTRY_LOCAL_VAR = 3; +} +struct Entry { + unsigned byte type; + boolean has_parent; + if { + Entry parent; + } + utf name; + boolean has_javadoc; + if { + utf javadoc; + } + if { + utf descriptor; + } + if { + unsigned short index; + boolean parameter; + } +} +``` +- `type`: The type of entry this is. One of `ENTRY_CLASS`, `ENTRY_FIELD`, `ENTRY_METHOD` or `ENTRY_LOCAL_VAR`. +- `parent`: The parent entry. Only class entries may have no parent. fields, methods and inner classes must have their + containing class as their parent. Local variables have a method as a parent. +- `name`: The class/field/method/variable name. +- `javadoc`: The javadoc of an entry, if present. +- `descriptor`: The field/method descriptor. +- `index`: The index of the local variable in the local variable table. +- `parameter`: Whether the local variable is a parameter. + +### The Message struct +```c +enum MessageType { + MESSAGE_CHAT = 0, + MESSAGE_CONNECT = 1, + MESSAGE_DISCONNECT = 2, + MESSAGE_EDIT_DOCS = 3, + MESSAGE_MARK_DEOBF = 4, + MESSAGE_REMOVE_MAPPING = 5, + MESSAGE_RENAME = 6 +}; +typedef unsigned byte message_type_t; + +struct Message { + message_type_t type; + union { // Note that the size of this varies depending on type, it is not constant size + struct { + utf user; + utf message; + } chat; + struct { + utf user; + } connect; + struct { + utf user; + } disconnect; + struct { + utf user; + Entry entry; + } edit_docs; + struct { + utf user; + Entry entry; + } mark_deobf; + struct { + utf user; + Entry entry; + } remove_mapping; + struct { + utf user; + Entry entry; + utf new_name; + } rename; + } data; +}; +``` +- `type`: The type of message this is. One of `MESSAGE_CHAT`, `MESSAGE_CONNECT`, `MESSAGE_DISCONNECT`, + `MESSAGE_EDIT_DOCS`, `MESSAGE_MARK_DEOBF`, `MESSAGE_REMOVE_MAPPING`, `MESSAGE_RENAME`. +- `chat`: Chat message. Use in case `type` is `MESSAGE_CHAT` +- `connect`: Sent when a user connects. Use in case `type` is `MESSAGE_CONNECT` +- `disconnect`: Sent when a user disconnects. Use in case `type` is `MESSAGE_DISCONNECT` +- `edit_docs`: Sent when a user edits the documentation of an entry. Use in case `type` is `MESSAGE_EDIT_DOCS` +- `mark_deobf`: Sent when a user marks an entry as deobfuscated. Use in case `type` is `MESSAGE_MARK_DEOBF` +- `remove_mapping`: Sent when a user removes a mapping. Use in case `type` is `MESSAGE_REMOVE_MAPPING` +- `rename`: Sent when a user renames an entry. Use in case `type` is `MESSAGE_RENAME` +- `user`: The user that performed the action. +- `message`: The message the user sent. +- `entry`: The entry that was modified. +- `new_name`: The new name for the entry. + + +### Login (client-to-server) +```c +struct LoginC2SPacket { + unsigned short protocol_version; + byte checksum[20]; + unsigned byte password_length; + char password[password_length]; + utf username; +} +``` +- `protocol_version`: the version of the protocol. If the version does not match on the server, then the client will be + kicked immediately. Currently always equal to 0. +- `checksum`: the SHA-1 hash of the JAR file the client has open. If this does not match the SHA-1 hash of the JAR file + the server has open, the client will be kicked. +- `password`: the password needed to log into the server. Note that each `char` is 2 bytes, as per the Java data type. + If this password is incorrect, the client will be kicked. +- `username`: the username of the user logging in. If the username is not unique, the client will be kicked. + +### ConfirmChange (client-to-server) +```c +struct ConfirmChangeC2SPacket { + unsigned short sync_id; +} +``` +- `sync_id`: the sync ID to confirm. + +### Rename (client-to-server) +```c +struct RenameC2SPacket { + Entry obf_entry; + utf new_name; + boolean refresh_class_tree; +} +``` +- `obf_entry`: the obfuscated name and descriptor of the entry to rename. +- `new_name`: what to rename the entry to. +- `refresh_class_tree`: whether the class tree on the sidebar of Enigma needs refreshing as a result of this change. + +### RemoveMapping (client-to-server) +```c +struct RemoveMappingC2SPacket { + Entry obf_entry; +} +``` +- `obf_entry`: the obfuscated name and descriptor of the entry to remove the mapping for. + +### ChangeDocs (client-to-server) +```c +struct ChangeDocsC2SPacket { + Entry obf_entry; + utf new_docs; +} +``` +- `obf_entry`: the obfuscated name and descriptor of the entry to change the documentation for. +- `new_docs`: the new documentation for this entry, or an empty string to remove the documentation. + +### MarkDeobfuscated (client-to-server) +```c +struct MarkDeobfuscatedC2SPacket { + Entry obf_entry; +} +``` +- `obf_entry`: the obfuscated name and descriptor of the entry to mark as deobfuscated. + +### Message (client-to-server) +```c +struct MessageC2SPacket { + utf message; +} +``` +- `message`: The text message the user sent. + +### Kick (server-to-client) +```c +struct KickS2CPacket { + utf reason; +} +``` +- `reason`: the reason for the kick, may or may not be a translation key for the client to display to the user. + +### SyncMappings (server-to-client) +```c +struct SyncMappingsS2CPacket { + int num_roots; + MappingNode roots[num_roots]; +} +struct MappingNode { + NoParentEntry obf_entry; + boolean is_named; + if { + utf name; + boolean has_javadoc; + if { + utf javadoc; + } + } + unsigned short children_count; + MappingNode children[children_count]; +} +typedef { Entry but without the has_parent or parent fields } NoParentEntry; +``` +- `roots`: The root mapping nodes, containing all the entries without parents. +- `obf_entry`: The value of a node, containing the obfuscated name and descriptor of the entry. +- `name`: The deobfuscated name of the entry, if it has a mapping. +- `javadoc`: The documentation for the entry, if it is named and has documentation. +- `children`: The children of this node + +### Rename (server-to-client) +```c +struct RenameS2CPacket { + unsigned short sync_id; + Entry obf_entry; + utf new_name; + boolean refresh_class_tree; +} +``` +- `sync_id`: the sync ID of the change for locking purposes. +- `obf_entry`: the obfuscated name and descriptor of the entry to rename. +- `new_name`: what to rename the entry to. +- `refresh_class_tree`: whether the class tree on the sidebar of Enigma needs refreshing as a result of this change. + +### RemoveMapping (server-to-client) +```c +struct RemoveMappingS2CPacket { + unsigned short sync_id; + Entry obf_entry; +} +``` +- `sync_id`: the sync ID of the change for locking purposes. +- `obf_entry`: the obfuscated name and descriptor of the entry to remove the mapping for. + +### ChangeDocs (server-to-client) +```c +struct ChangeDocsS2CPacket { + unsigned short sync_id; + Entry obf_entry; + utf new_docs; +} +``` +- `sync_id`: the sync ID of the change for locking purposes. +- `obf_entry`: the obfuscated name and descriptor of the entry to change the documentation for. +- `new_docs`: the new documentation for this entry, or an empty string to remove the documentation. + +### MarkDeobfuscated (server-to-client) +```c +struct MarkDeobfuscatedS2CPacket { + unsigned short sync_id; + Entry obf_entry; +} +``` +- `sync_id`: the sync ID of the change for locking purposes. +- `obf_entry`: the obfuscated name and descriptor of the entry to mark as deobfuscated. + +### Message (server-to-client) +```c +struct MessageS2CPacket { + Message message; +} +``` + +### UserList (server-to-client) +```c +struct UserListS2CPacket { + unsigned short len; + utf user[len]; +} +``` -- cgit v1.2.3