diff options
Diffstat (limited to 'enigma-server/docs')
| -rw-r--r-- | enigma-server/docs/protocol.md | 366 |
1 files changed, 366 insertions, 0 deletions
diff --git a/enigma-server/docs/protocol.md b/enigma-server/docs/protocol.md new file mode 100644 index 0000000..c14ecb8 --- /dev/null +++ b/enigma-server/docs/protocol.md | |||
| @@ -0,0 +1,366 @@ | |||
| 1 | # Enigma protocol | ||
| 2 | Enigma uses TCP sockets for communication. Data is sent in each direction as a continuous stream, with packets being | ||
| 3 | concatenated one after the other. | ||
| 4 | |||
| 5 | In this document, data will be represented in C-like pseudocode. The primitive data types will be the same as those | ||
| 6 | defined by Java's [DataOutputStream](https://docs.oracle.com/javase/7/docs/api/java/io/DataOutputStream.html), i.e. in | ||
| 7 | big-endian order for multi-byte integers (`short`, `int` and `long`). The one exception is for Strings, which do *not* | ||
| 8 | use the same modified UTF format as in `DataOutputStream`, I repeat, the normal `writeUTF` method in `DataOutputStream` | ||
| 9 | (and the corresponding method in `DataInputStream`) should *not* be used. Instead, there is a custom `utf` struct for | ||
| 10 | Strings, see below. | ||
| 11 | |||
| 12 | ## Login protocol | ||
| 13 | ``` | ||
| 14 | Client Server | ||
| 15 | | | | ||
| 16 | | Login | | ||
| 17 | | >>>>>>>>>>>>> | | ||
| 18 | | | | ||
| 19 | | SyncMappings | | ||
| 20 | | <<<<<<<<<<<<< | | ||
| 21 | | | | ||
| 22 | | ConfirmChange | | ||
| 23 | | >>>>>>>>>>>>> | | ||
| 24 | ``` | ||
| 25 | 1. On connect, the client sends a login packet to the server. This allows the server to test the validity of the client, | ||
| 26 | as well as allowing the client to declare metadata about itself, such as the username. | ||
| 27 | 1. After validating the login packet, the server sends all its mappings to the client, and the client will apply them. | ||
| 28 | 1. Upon receiving the mappings, the client sends a `ConfirmChange` packet with `sync_id` set to 0, to confirm that it | ||
| 29 | has received the mappings and is in sync with the server. Once the server receives this packet, the client will be | ||
| 30 | allowed to modify mappings. | ||
| 31 | |||
| 32 | The server will not accept any other packets from the client until this entire exchange has been completed. | ||
| 33 | |||
| 34 | ## Kicking clients | ||
| 35 | When the server kicks a client, it may optionally send a `Kick` packet immediately before closing the connection, which | ||
| 36 | contains the reason why the client was kicked (so the client can display it to the user). This is not required though - | ||
| 37 | the server may simply terminate the connection. | ||
| 38 | |||
| 39 | ## Changing mappings | ||
| 40 | This section uses the example of renaming, but the same pattern applies to all mapping changes. | ||
| 41 | ``` | ||
| 42 | Client A Server Client B | ||
| 43 | | | | | ||
| 44 | | RenameC2S | | | ||
| 45 | | >>>>>>>>> | | | ||
| 46 | | | | | ||
| 47 | | | RenameS2C | | ||
| 48 | | | >>>>>>>>>>>>> | | ||
| 49 | | | | | ||
| 50 | | | ConfirmChange | | ||
| 51 | | | <<<<<<<<<<<<< | | ||
| 52 | ``` | ||
| 53 | |||
| 54 | 1. Client A validates the name and updates the mapping client-side to give the impression there is no latency >:) | ||
| 55 | 1. Client A sends a rename packet to the server, notifying it of the rename. | ||
| 56 | 1. The server assesses the validity of the rename. If it is invalid for whatever reason (e.g. the mapping was locked or | ||
| 57 | the name contains invalid characters), then the server sends an appropriate packet back to client A to revert the | ||
| 58 | change, with `sync_id` set to 0. The server will ignore any `ConfirmChange` packets it receives in response to this. | ||
| 59 | 1. If the rename was valid, the server will lock all clients except client A from being able to modify this mapping, and | ||
| 60 | then send an appropriate packet to all clients except client A notifying them of this rename. The `sync_id` will be a | ||
| 61 | unique non-zero value identifying this change. | ||
| 62 | 1. Each client responds to this packet by updating their mappings locally to reflect this change, then sending a | ||
| 63 | `ConfirmChange` packet with the same `sync_id` as the one in the packet they received, to confirm that they have | ||
| 64 | received the change. | ||
| 65 | 1. When the server receives the `ConfirmChange` packet, and another change to that mapping hasn't occurred since, the | ||
| 66 | server will unlock that mapping for that client and allow them to make changes again. | ||
| 67 | |||
| 68 | ## Packets | ||
| 69 | ```c | ||
| 70 | struct Packet { | ||
| 71 | unsigned short packet_id; | ||
| 72 | data[]; // depends on packet_id | ||
| 73 | } | ||
| 74 | ``` | ||
| 75 | The IDs for client-to-server packets are as follows: | ||
| 76 | - 0: `Login` | ||
| 77 | - 1: `ConfirmChange` | ||
| 78 | - 2: `Rename` | ||
| 79 | - 3: `RemoveMapping` | ||
| 80 | - 4: `ChangeDocs` | ||
| 81 | - 5: `MarkDeobfuscated` | ||
| 82 | - 6: `Message` | ||
| 83 | |||
| 84 | The IDs for server-to-client packets are as follows: | ||
| 85 | - 0: `Kick` | ||
| 86 | - 1: `SyncMappings` | ||
| 87 | - 2: `Rename` | ||
| 88 | - 3: `RemoveMapping` | ||
| 89 | - 4: `ChangeDocs` | ||
| 90 | - 5: `MarkDeobfuscated` | ||
| 91 | - 6: `Message` | ||
| 92 | - 7: `UserList` | ||
| 93 | |||
| 94 | ### The utf struct | ||
| 95 | ```c | ||
| 96 | struct utf { | ||
| 97 | unsigned short length; | ||
| 98 | byte data[length]; | ||
| 99 | } | ||
| 100 | ``` | ||
| 101 | - `length`: The number of bytes in the UTF-8 encoding of the string. Note, this may not be the same as the number of | ||
| 102 | Unicode characters in the string. | ||
| 103 | - `data`: A standard UTF-8 encoded byte array representing the string. | ||
| 104 | |||
| 105 | ### The Entry struct | ||
| 106 | ```c | ||
| 107 | enum EntryType { | ||
| 108 | ENTRY_CLASS = 0, ENTRY_FIELD = 1, ENTRY_METHOD = 2, ENTRY_LOCAL_VAR = 3; | ||
| 109 | } | ||
| 110 | struct Entry { | ||
| 111 | unsigned byte type; | ||
| 112 | boolean has_parent; | ||
| 113 | if<has_parent> { | ||
| 114 | Entry parent; | ||
| 115 | } | ||
| 116 | utf name; | ||
| 117 | boolean has_javadoc; | ||
| 118 | if<has_javadoc> { | ||
| 119 | utf javadoc; | ||
| 120 | } | ||
| 121 | if<type == ENTRY_FIELD || type == ENTRY_METHOD> { | ||
| 122 | utf descriptor; | ||
| 123 | } | ||
| 124 | if<type == ENTRY_LOCAL_VAR> { | ||
| 125 | unsigned short index; | ||
| 126 | boolean parameter; | ||
| 127 | } | ||
| 128 | } | ||
| 129 | ``` | ||
| 130 | - `type`: The type of entry this is. One of `ENTRY_CLASS`, `ENTRY_FIELD`, `ENTRY_METHOD` or `ENTRY_LOCAL_VAR`. | ||
| 131 | - `parent`: The parent entry. Only class entries may have no parent. fields, methods and inner classes must have their | ||
| 132 | containing class as their parent. Local variables have a method as a parent. | ||
| 133 | - `name`: The class/field/method/variable name. | ||
| 134 | - `javadoc`: The javadoc of an entry, if present. | ||
| 135 | - `descriptor`: The field/method descriptor. | ||
| 136 | - `index`: The index of the local variable in the local variable table. | ||
| 137 | - `parameter`: Whether the local variable is a parameter. | ||
| 138 | |||
| 139 | ### The Message struct | ||
| 140 | ```c | ||
| 141 | enum MessageType { | ||
| 142 | MESSAGE_CHAT = 0, | ||
| 143 | MESSAGE_CONNECT = 1, | ||
| 144 | MESSAGE_DISCONNECT = 2, | ||
| 145 | MESSAGE_EDIT_DOCS = 3, | ||
| 146 | MESSAGE_MARK_DEOBF = 4, | ||
| 147 | MESSAGE_REMOVE_MAPPING = 5, | ||
| 148 | MESSAGE_RENAME = 6 | ||
| 149 | }; | ||
| 150 | typedef unsigned byte message_type_t; | ||
| 151 | |||
| 152 | struct Message { | ||
| 153 | message_type_t type; | ||
| 154 | union { // Note that the size of this varies depending on type, it is not constant size | ||
| 155 | struct { | ||
| 156 | utf user; | ||
| 157 | utf message; | ||
| 158 | } chat; | ||
| 159 | struct { | ||
| 160 | utf user; | ||
| 161 | } connect; | ||
| 162 | struct { | ||
| 163 | utf user; | ||
| 164 | } disconnect; | ||
| 165 | struct { | ||
| 166 | utf user; | ||
| 167 | Entry entry; | ||
| 168 | } edit_docs; | ||
| 169 | struct { | ||
| 170 | utf user; | ||
| 171 | Entry entry; | ||
| 172 | } mark_deobf; | ||
| 173 | struct { | ||
| 174 | utf user; | ||
| 175 | Entry entry; | ||
| 176 | } remove_mapping; | ||
| 177 | struct { | ||
| 178 | utf user; | ||
| 179 | Entry entry; | ||
| 180 | utf new_name; | ||
| 181 | } rename; | ||
| 182 | } data; | ||
| 183 | }; | ||
| 184 | ``` | ||
| 185 | - `type`: The type of message this is. One of `MESSAGE_CHAT`, `MESSAGE_CONNECT`, `MESSAGE_DISCONNECT`, | ||
| 186 | `MESSAGE_EDIT_DOCS`, `MESSAGE_MARK_DEOBF`, `MESSAGE_REMOVE_MAPPING`, `MESSAGE_RENAME`. | ||
| 187 | - `chat`: Chat message. Use in case `type` is `MESSAGE_CHAT` | ||
| 188 | - `connect`: Sent when a user connects. Use in case `type` is `MESSAGE_CONNECT` | ||
| 189 | - `disconnect`: Sent when a user disconnects. Use in case `type` is `MESSAGE_DISCONNECT` | ||
| 190 | - `edit_docs`: Sent when a user edits the documentation of an entry. Use in case `type` is `MESSAGE_EDIT_DOCS` | ||
| 191 | - `mark_deobf`: Sent when a user marks an entry as deobfuscated. Use in case `type` is `MESSAGE_MARK_DEOBF` | ||
| 192 | - `remove_mapping`: Sent when a user removes a mapping. Use in case `type` is `MESSAGE_REMOVE_MAPPING` | ||
| 193 | - `rename`: Sent when a user renames an entry. Use in case `type` is `MESSAGE_RENAME` | ||
| 194 | - `user`: The user that performed the action. | ||
| 195 | - `message`: The message the user sent. | ||
| 196 | - `entry`: The entry that was modified. | ||
| 197 | - `new_name`: The new name for the entry. | ||
| 198 | |||
| 199 | |||
| 200 | ### Login (client-to-server) | ||
| 201 | ```c | ||
| 202 | struct LoginC2SPacket { | ||
| 203 | unsigned short protocol_version; | ||
| 204 | byte checksum[20]; | ||
| 205 | unsigned byte password_length; | ||
| 206 | char password[password_length]; | ||
| 207 | utf username; | ||
| 208 | } | ||
| 209 | ``` | ||
| 210 | - `protocol_version`: the version of the protocol. If the version does not match on the server, then the client will be | ||
| 211 | kicked immediately. Currently always equal to 0. | ||
| 212 | - `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 | ||
| 213 | the server has open, the client will be kicked. | ||
| 214 | - `password`: the password needed to log into the server. Note that each `char` is 2 bytes, as per the Java data type. | ||
| 215 | If this password is incorrect, the client will be kicked. | ||
| 216 | - `username`: the username of the user logging in. If the username is not unique, the client will be kicked. | ||
| 217 | |||
| 218 | ### ConfirmChange (client-to-server) | ||
| 219 | ```c | ||
| 220 | struct ConfirmChangeC2SPacket { | ||
| 221 | unsigned short sync_id; | ||
| 222 | } | ||
| 223 | ``` | ||
| 224 | - `sync_id`: the sync ID to confirm. | ||
| 225 | |||
| 226 | ### Rename (client-to-server) | ||
| 227 | ```c | ||
| 228 | struct RenameC2SPacket { | ||
| 229 | Entry obf_entry; | ||
| 230 | utf new_name; | ||
| 231 | boolean refresh_class_tree; | ||
| 232 | } | ||
| 233 | ``` | ||
| 234 | - `obf_entry`: the obfuscated name and descriptor of the entry to rename. | ||
| 235 | - `new_name`: what to rename the entry to. | ||
| 236 | - `refresh_class_tree`: whether the class tree on the sidebar of Enigma needs refreshing as a result of this change. | ||
| 237 | |||
| 238 | ### RemoveMapping (client-to-server) | ||
| 239 | ```c | ||
| 240 | struct RemoveMappingC2SPacket { | ||
| 241 | Entry obf_entry; | ||
| 242 | } | ||
| 243 | ``` | ||
| 244 | - `obf_entry`: the obfuscated name and descriptor of the entry to remove the mapping for. | ||
| 245 | |||
| 246 | ### ChangeDocs (client-to-server) | ||
| 247 | ```c | ||
| 248 | struct ChangeDocsC2SPacket { | ||
| 249 | Entry obf_entry; | ||
| 250 | utf new_docs; | ||
| 251 | } | ||
| 252 | ``` | ||
| 253 | - `obf_entry`: the obfuscated name and descriptor of the entry to change the documentation for. | ||
| 254 | - `new_docs`: the new documentation for this entry, or an empty string to remove the documentation. | ||
| 255 | |||
| 256 | ### MarkDeobfuscated (client-to-server) | ||
| 257 | ```c | ||
| 258 | struct MarkDeobfuscatedC2SPacket { | ||
| 259 | Entry obf_entry; | ||
| 260 | } | ||
| 261 | ``` | ||
| 262 | - `obf_entry`: the obfuscated name and descriptor of the entry to mark as deobfuscated. | ||
| 263 | |||
| 264 | ### Message (client-to-server) | ||
| 265 | ```c | ||
| 266 | struct MessageC2SPacket { | ||
| 267 | utf message; | ||
| 268 | } | ||
| 269 | ``` | ||
| 270 | - `message`: The text message the user sent. | ||
| 271 | |||
| 272 | ### Kick (server-to-client) | ||
| 273 | ```c | ||
| 274 | struct KickS2CPacket { | ||
| 275 | utf reason; | ||
| 276 | } | ||
| 277 | ``` | ||
| 278 | - `reason`: the reason for the kick, may or may not be a translation key for the client to display to the user. | ||
| 279 | |||
| 280 | ### SyncMappings (server-to-client) | ||
| 281 | ```c | ||
| 282 | struct SyncMappingsS2CPacket { | ||
| 283 | int num_roots; | ||
| 284 | MappingNode roots[num_roots]; | ||
| 285 | } | ||
| 286 | struct MappingNode { | ||
| 287 | NoParentEntry obf_entry; | ||
| 288 | boolean is_named; | ||
| 289 | if<is_named> { | ||
| 290 | utf name; | ||
| 291 | boolean has_javadoc; | ||
| 292 | if<has_javadoc> { | ||
| 293 | utf javadoc; | ||
| 294 | } | ||
| 295 | } | ||
| 296 | unsigned short children_count; | ||
| 297 | MappingNode children[children_count]; | ||
| 298 | } | ||
| 299 | typedef { Entry but without the has_parent or parent fields } NoParentEntry; | ||
| 300 | ``` | ||
| 301 | - `roots`: The root mapping nodes, containing all the entries without parents. | ||
| 302 | - `obf_entry`: The value of a node, containing the obfuscated name and descriptor of the entry. | ||
| 303 | - `name`: The deobfuscated name of the entry, if it has a mapping. | ||
| 304 | - `javadoc`: The documentation for the entry, if it is named and has documentation. | ||
| 305 | - `children`: The children of this node | ||
| 306 | |||
| 307 | ### Rename (server-to-client) | ||
| 308 | ```c | ||
| 309 | struct RenameS2CPacket { | ||
| 310 | unsigned short sync_id; | ||
| 311 | Entry obf_entry; | ||
| 312 | utf new_name; | ||
| 313 | boolean refresh_class_tree; | ||
| 314 | } | ||
| 315 | ``` | ||
| 316 | - `sync_id`: the sync ID of the change for locking purposes. | ||
| 317 | - `obf_entry`: the obfuscated name and descriptor of the entry to rename. | ||
| 318 | - `new_name`: what to rename the entry to. | ||
| 319 | - `refresh_class_tree`: whether the class tree on the sidebar of Enigma needs refreshing as a result of this change. | ||
| 320 | |||
| 321 | ### RemoveMapping (server-to-client) | ||
| 322 | ```c | ||
| 323 | struct RemoveMappingS2CPacket { | ||
| 324 | unsigned short sync_id; | ||
| 325 | Entry obf_entry; | ||
| 326 | } | ||
| 327 | ``` | ||
| 328 | - `sync_id`: the sync ID of the change for locking purposes. | ||
| 329 | - `obf_entry`: the obfuscated name and descriptor of the entry to remove the mapping for. | ||
| 330 | |||
| 331 | ### ChangeDocs (server-to-client) | ||
| 332 | ```c | ||
| 333 | struct ChangeDocsS2CPacket { | ||
| 334 | unsigned short sync_id; | ||
| 335 | Entry obf_entry; | ||
| 336 | utf new_docs; | ||
| 337 | } | ||
| 338 | ``` | ||
| 339 | - `sync_id`: the sync ID of the change for locking purposes. | ||
| 340 | - `obf_entry`: the obfuscated name and descriptor of the entry to change the documentation for. | ||
| 341 | - `new_docs`: the new documentation for this entry, or an empty string to remove the documentation. | ||
| 342 | |||
| 343 | ### MarkDeobfuscated (server-to-client) | ||
| 344 | ```c | ||
| 345 | struct MarkDeobfuscatedS2CPacket { | ||
| 346 | unsigned short sync_id; | ||
| 347 | Entry obf_entry; | ||
| 348 | } | ||
| 349 | ``` | ||
| 350 | - `sync_id`: the sync ID of the change for locking purposes. | ||
| 351 | - `obf_entry`: the obfuscated name and descriptor of the entry to mark as deobfuscated. | ||
| 352 | |||
| 353 | ### Message (server-to-client) | ||
| 354 | ```c | ||
| 355 | struct MessageS2CPacket { | ||
| 356 | Message message; | ||
| 357 | } | ||
| 358 | ``` | ||
| 359 | |||
| 360 | ### UserList (server-to-client) | ||
| 361 | ```c | ||
| 362 | struct UserListS2CPacket { | ||
| 363 | unsigned short len; | ||
| 364 | utf user[len]; | ||
| 365 | } | ||
| 366 | ``` | ||