# Client/Server Protocol This document describes the TCP protocol design for remote mode operation. ## Protocol Overview The Implexus client/server protocol uses a simple binary message format over TCP. Each message consists of a header and payload. ```mermaid sequenceDiagram participant Client participant Server Client->>Server: Connect Server-->>Client: Welcome Message loop Operations Client->>Server: Request Server-->>Client: Response end Client->>Server: Disconnect Server-->>Client: Goodbye ``` ## Message Format ### Header All messages start with a common header: ``` Offset Size Field 0 4 Magic: 0x49 0x4D 0x50 0x58 ("IMPX") 4 1 Message type 5 4 Payload length (big-endian) 9 2 Request ID (for request/response matching) 11 ... Payload ``` ### Message Types | Code | Type | Direction | |------|------|-----------| | 0x00 | WELCOME | Server → Client | | 0x01 | GOODBYE | Server → Client | | 0x10 | GET_ENTITY | Client → Server | | 0x11 | ENTITY_RESPONSE | Server → Client | | 0x12 | ENTITY_NOT_FOUND | Server → Client | | 0x13 | ENTITY_EXISTS | Client → Server | | 0x14 | BOOLEAN_RESPONSE | Server → Client | | 0x20 | CREATE_CONTAINER | Client → Server | | 0x21 | CREATE_DOCUMENT | Client → Server | | 0x22 | CREATE_CATEGORY | Client → Server | | 0x23 | CREATE_INDEX | Client → Server | | 0x30 | SET_PROPERTY | Client → Server | | 0x31 | GET_PROPERTY | Client → Server | | 0x32 | REMOVE_PROPERTY | Client → Server | | 0x40 | DELETE_ENTITY | Client → Server | | 0x41 | GET_CHILDREN | Client → Server | | 0x42 | GET_CHILD_NAMES | Client → Server | | 0x50 | QUERY_BY_TYPE | Client → Server | | 0x51 | QUERY_BY_EXPRESSION | Client → Server | | 0x60 | BEGIN_TRANSACTION | Client → Server | | 0x61 | COMMIT_TRANSACTION | Client → Server | | 0x62 | ROLLBACK_TRANSACTION | Client → Server | | 0x70 | ERROR | Server → Client | | 0x7F | SUCCESS | Server → Client | ## Protocol Classes ### Message Base ```vala namespace Implexus.Protocol { public interface Message : Object { public abstract uint8 message_type { get; } public abstract uint8[] serialize(); public abstract void deserialize(uint8[] data) throws ProtocolError; } public class MessageHeader { public uint8[] magic { get; set; } public uint8 message_type { get; set; } public uint32 payload_length { get; set; } public uint16 request_id { get; set; } public static const int SIZE = 11; public MessageHeader() { magic = new uint8[] { 0x49, 0x4D, 0x50, 0x58 }; } public uint8[] serialize() { var data = new uint8[SIZE]; data[0] = magic[0]; data[1] = magic[1]; data[2] = magic[2]; data[3] = magic[3]; data[4] = message_type; data[5] = (uint8) (payload_length >> 24); data[6] = (uint8) (payload_length >> 16); data[7] = (uint8) (payload_length >> 8); data[8] = (uint8) payload_length; data[9] = (uint8) (request_id >> 8); data[10] = (uint8) request_id; return data; } public static MessageHeader deserialize(uint8[] data) throws ProtocolError { if (data.length < SIZE) { throw new ProtocolError.INVALID_MESSAGE("Header too short"); } var header = new MessageHeader(); header.magic = data[0:4]; if (header.magic[0] != 'I' || header.magic[1] != 'M' || header.magic[2] != 'P' || header.magic[3] != 'X') { throw new ProtocolError.INVALID_MESSAGE("Invalid magic"); } header.message_type = data[4]; header.payload_length = ((uint32) data[5] << 24) | ((uint32) data[6] << 16) | ((uint32) data[7] << 8) | ((uint32) data[8]); header.request_id = (uint16) ((data[9] << 8) | data[10]); return header; } } } // namespace Implexus.Protocol ``` ### Request Interface ```vala namespace Implexus.Protocol { public interface Request : Object, Message { public abstract uint16 request_id { get; set; } } } // namespace Implexus.Protocol ``` ### Response Interface ```vala namespace Implexus.Protocol { public interface Response : Object, Message { public abstract uint16 request_id { get; set; } public abstract bool is_success { get; } } } // namespace Implexus.Protocol ``` ## Request/Response Classes ### GetEntityRequest ```vala namespace Implexus.Protocol { public class GetEntityRequest : Object, Request { private uint16 _request_id; private Path _path; public uint8 message_type { get { return 0x10; } } public uint16 request_id { get { return _request_id; } set { _request_id = value; } } public Path path { get { return _path; } set { _path = value; } } public GetEntityRequest() { _path = new Path.root(); } public GetEntityRequest.for_path(Path path) { _path = path; } public uint8[] serialize() { var writer = new ElementWriter(); writer.write_string(_path.to_string()); return writer.to_bytes(); } public void deserialize(uint8[] data) throws ProtocolError { try { var reader = new ElementReader(data); var path_str = reader.read_string(); _path = new Path(path_str); } catch (SerializationError e) { throw new ProtocolError.INVALID_MESSAGE("Failed to deserialize: %s", e.message); } } } } // namespace Implexus.Protocol ``` ### EntityResponse ```vala namespace Implexus.Protocol { public class EntityResponse : Object, Response { private uint16 _request_id; private EntityData _entity_data; public uint8 message_type { get { return 0x11; } } public uint16 request_id { get { return _request_id; } set { _request_id = value; } } public bool is_success { get { return true; } } public EntityData entity_data { get { return _entity_data; } set { _entity_data = value; } } public uint8[] serialize() { var writer = new ElementWriter(); // Entity type writer.write_uint8((uint8) _entity_data.entity_type); // Path writer.write_string(_entity_data.path.to_string()); // Type label (for documents) writer.write_string(_entity_data.type_label ?? ""); // Expression (for category/index) writer.write_string(_entity_data.expression ?? ""); // Properties (for documents) if (_entity_data.properties != null) { writer.write_dictionary(_entity_data.properties); } else { writer.write_null(); } return writer.to_bytes(); } public void deserialize(uint8[] data) throws ProtocolError { try { var reader = new ElementReader(data); var entity_type = (EntityType) reader.read_uint8(); var path = new Path(reader.read_string()); var type_label = reader.read_string(); var expression = reader.read_string(); _entity_data = new EntityData(); _entity_data.entity_type = entity_type; _entity_data.path = path; _entity_data.type_label = type_label.length > 0 ? type_label : null; _entity_data.expression = expression.length > 0 ? expression : null; // Properties var props_element = reader.read_element(); if (props_element != null && !props_element.is_null()) { // Convert to dictionary } } catch (SerializationError e) { throw new ProtocolError.INVALID_MESSAGE("Failed to deserialize: %s", e.message); } } } public class EntityData { public EntityType entity_type; public Path path; public string? type_label; public string? expression; public Invercargill.DataStructures.Dictionary? properties; } } // namespace Implexus.Protocol ``` ### ErrorResponse ```vala namespace Implexus.Protocol { public class ErrorResponse : Object, Response { private uint16 _request_id; private EngineError _error; public uint8 message_type { get { return 0x70; } } public uint16 request_id { get { return _request_id; } set { _request_id = value; } } public bool is_success { get { return false; } } public EngineError error { get { return _error; } set { _error = value; } } public uint8[] serialize() { var writer = new ElementWriter(); // Error code writer.write_uint8((uint8) _error.code); // Message writer.write_string(_error.message); return writer.to_bytes(); } public void deserialize(uint8[] data) throws ProtocolError { try { var reader = new ElementReader(data); var code = (EngineError.Code) reader.read_uint8(); var message = reader.read_string(); _error = new EngineError(code, message); } catch (SerializationError e) { throw new ProtocolError.INVALID_MESSAGE("Failed to deserialize: %s", e.message); } } } } // namespace Implexus.Protocol ``` ## MessageReader ```vala namespace Implexus.Protocol { public class MessageReader { private InputStream _stream; private uint8[] _header_buffer; public MessageReader(InputStream stream) { _stream = stream; _header_buffer = new uint8[MessageHeader.SIZE]; } public Message? read_message() throws ProtocolError { try { // Read header size_t bytes_read; _stream.read_all(_header_buffer, out bytes_read); if (bytes_read == 0) { return null; // Connection closed } if (bytes_read < MessageHeader.SIZE) { throw new ProtocolError.INVALID_MESSAGE("Incomplete header"); } var header = MessageHeader.deserialize(_header_buffer); // Read payload var payload = new uint8[header.payload_length]; if (header.payload_length > 0) { _stream.read_all(payload, out bytes_read); if (bytes_read < header.payload_length) { throw new ProtocolError.INVALID_MESSAGE("Incomplete payload"); } } // Create message based on type return create_message(header, payload); } catch (IOError e) { throw new ProtocolError.IO_ERROR("Read error: %s", e.message); } } private Message create_message(MessageHeader header, uint8[] payload) throws ProtocolError { Message message; switch (header.message_type) { case 0x00: message = new WelcomeMessage(); break; case 0x11: message = new EntityResponse(); break; case 0x12: message = new EntityNotFoundResponse(); break; case 0x14: message = new BooleanResponse(); break; case 0x70: message = new ErrorResponse(); break; case 0x7F: message = new SuccessResponse(); break; default: throw new ProtocolError.UNKNOWN_MESSAGE_TYPE( "Unknown message type: 0x%02X", header.message_type ); } message.request_id = header.request_id; if (payload.length > 0) { message.deserialize(payload); } return message; } public Response read_response() throws ProtocolError { var message = read_message(); if (message == null) { throw new ProtocolError.IO_ERROR("Connection closed"); } if (!(message is Response)) { throw new ProtocolError.INVALID_MESSAGE("Expected response"); } return (Response) message; } } } // namespace Implexus.Protocol ``` ## MessageWriter ```vala namespace Implexus.Protocol { public class MessageWriter { private OutputStream _stream; private uint16 _next_request_id; public MessageWriter(OutputStream stream) { _stream = stream; _next_request_id = 1; } public void write_message(Message message) throws ProtocolError { try { // Get payload var payload = message.serialize(); // Create header var header = new MessageHeader(); header.message_type = message.message_type; header.payload_length = (uint32) payload.length; header.request_id = message.request_id; // Write header var header_data = header.serialize(); _stream.write(header_data); // Write payload if (payload.length > 0) { _stream.write(payload); } _stream.flush(); } catch (IOError e) { throw new ProtocolError.IO_ERROR("Write error: %s", e.message); } } public uint16 write_request(Request request) throws ProtocolError { request.request_id = _next_request_id++; write_message(request); return request.request_id; } } } // namespace Implexus.Protocol ``` ## Server Implementation ### Server Class ```vala namespace Implexus.Server { public class Server : Object { private Engine _engine; private SocketService _socket_service; private uint16 _port; private bool _running; public signal void client_connected(); public signal void client_disconnected(); public Server(Engine engine, uint16 port = 9090) { _engine = engine; _port = port; _running = false; } public void start() throws ServerError { try { _socket_service = new SocketService(); _socket_service.add_inet_port(_port, null); _socket_service.incoming.connect(on_incoming); _running = true; print("Implexus server listening on port %d\n", _port); } catch (Error e) { throw new ServerError.STARTUP_FAILED("Failed to start server: %s", e.message); } } public void stop() { if (_socket_service != null) { _socket_service.stop(); _socket_service = null; } _running = false; } private bool on_incoming(SocketConnection connection) { client_connected(); // Handle in background new Thread("client", () => { handle_client(connection); return null; }); return true; } private void handle_client(SocketConnection connection) { var input = connection.get_input_stream(); var output = connection.get_output_stream(); var reader = new MessageReader(input); var writer = new MessageWriter(output); try { // Send welcome var welcome = new WelcomeMessage(); welcome.server_version = 1; writer.write_message(welcome); // Process requests while (true) { var message = reader.read_message(); if (message == null) break; var response = process_request(message); writer.write_message(response); } } catch (ProtocolError e) { warning("Protocol error: %s", e.message); } client_disconnected(); } private Response process_request(Message request) throws ProtocolError { try { switch (request.message_type) { case 0x10: // GET_ENTITY return handle_get_entity((GetEntityRequest) request); case 0x13: // ENTITY_EXISTS return handle_entity_exists((EntityExistsRequest) request); case 0x20: // CREATE_CONTAINER return handle_create_container((CreateContainerRequest) request); // ... other handlers default: return new ErrorResponse(EngineError.Code.PROTOCOL_ERROR, "Unknown request type"); } } catch (EngineError e) { return new ErrorResponse.from_error(e); } } private Response handle_get_entity(GetEntityRequest request) throws EngineError { var entity = _engine.get_entity(request.path); var response = new EntityResponse(); response.request_id = request.request_id; response.entity_data = entity_to_data(entity); return response; } private Response handle_entity_exists(EntityExistsRequest request) { var response = new BooleanResponse(); response.request_id = request.request_id; response.value = _engine.entity_exists(request.path); return response; } private EntityData entity_to_data(Entity entity) { var data = new EntityData(); data.entity_type = entity.entity_type; data.path = entity.path; data.type_label = entity.type_label; data.expression = entity.configured_expression; if (entity.entity_type == EntityType.DOCUMENT) { data.properties = new Invercargill.DataStructures.Dictionary(); foreach (var key in entity.properties.keys) { data.properties.set(key, entity.get_property(key)); } } return data; } } } // namespace Implexus.Server ``` ## Protocol Error ```vala namespace Implexus.Protocol { public errordomain ProtocolError { INVALID_MESSAGE, UNKNOWN_MESSAGE_TYPE, IO_ERROR, TIMEOUT, CONNECTION_CLOSED; } } // namespace Implexus.Protocol ``` ## Connection Flow ```mermaid sequenceDiagram participant C as Client participant S as Server participant E as Engine C->>S: TCP Connect S->>C: WELCOME version=1 C->>S: GET_ENTITY path=/users S->>E: get_entity with /users E-->>S: Entity S->>C: ENTITY_RESPONSE C->>S: CREATE_DOCUMENT path=/users/john type=User S->>E: create_document E-->>S: Document S->>C: ENTITY_RESPONSE C->>S: SET_PROPERTY path=/users/john name=email value=john@ex.com S->>E: set_property S->>C: SUCCESS C->>S: GET_ENTITY path=/nonexistent S->>E: get_entity E-->>S: throws ENTITY_NOT_FOUND S->>C: ENTITY_NOT_FOUND C->>S: TCP Close ``` ## Configuration ```vala namespace Implexus.Server { public class ServerConfiguration : Object { public uint16 port { get; set; default = 9090; } public int max_connections { get; set; default = 100; } public int timeout_seconds { get; set; default = 30; } public bool enable_tls { get; set; default = false; } public string? tls_cert_path { get; set; default = null; } public string? tls_key_path { get; set; default = null; } } } // namespace Implexus.Server ```