diff --git a/PROTOCOL.md b/PROTOCOL.md index 9ccfb61..ae00c32 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -45,12 +45,14 @@ PKT_PONG = 0x07 ## Handshake -Handshake is unencrypted and establishes a shared session key. +Handshake is unencrypted and establishes a shared session key. It also carries optional protocol metadata. Payload format: ``` [32 bytes] Ed25519 public key [N bytes] hostname (null‑terminated) +[1 byte] protocol version (optional) +[1 byte] protocol flags (optional) ``` Flow: @@ -60,6 +62,8 @@ Flow: 4. Client derives the same shared secret. 5. Both sides set `session.authenticated = true` and start encrypted traffic. +If version/flags are missing, peers assume protocol version 1 and flags 0. + ## Encryption For all non‑handshake packets: @@ -144,7 +148,7 @@ CTRL_DISCONNECT 0x07 ## Versioning Protocol version is in the header. Compatibility rules: -- Major version must match. +- Accept versions in `[PROTOCOL_MIN_VERSION, PROTOCOL_VERSION]`. - New fields should be appended to payloads with safe defaults. - Unknown packet types should be ignored. diff --git a/include/rootstream.h b/include/rootstream.h index 20017d9..440dd01 100644 --- a/include/rootstream.h +++ b/include/rootstream.h @@ -47,6 +47,9 @@ */ #define ROOTSTREAM_VERSION "1.0.0" +#define PROTOCOL_VERSION 1 +#define PROTOCOL_MIN_VERSION 1 +#define PROTOCOL_FLAGS 0 #define MAX_DISPLAYS 4 #define MAX_PACKET_SIZE 1400 #define MAX_PEERS 16 @@ -279,6 +282,8 @@ typedef struct { size_t video_rx_received; /* Bytes received so far */ uint64_t last_sent; /* Last outbound packet time (ms) */ uint64_t last_ping; /* Last keepalive ping time (ms) */ + uint8_t protocol_version; /* Peer protocol version */ + uint8_t protocol_flags; /* Peer protocol flags */ } peer_t; /* ============================================================================ diff --git a/src/network.c b/src/network.c index 91a1124..8cb4153 100644 --- a/src/network.c +++ b/src/network.c @@ -301,7 +301,7 @@ int rootstream_net_send_encrypted(rootstream_ctx_t *ctx, peer_t *peer, /* Build header */ hdr->magic = PACKET_MAGIC; - hdr->version = 1; + hdr->version = PROTOCOL_VERSION; hdr->type = type; hdr->flags = 0; hdr->nonce = nonce; @@ -431,10 +431,38 @@ int rootstream_net_recv(rootstream_ctx_t *ctx, int timeout_ms) { memcpy(peer_public_key, payload, CRYPTO_PUBLIC_KEY_BYTES); /* Extract hostname (null-terminated string after public key) */ - size_t hostname_len = hdr->payload_size - CRYPTO_PUBLIC_KEY_BYTES; - if (hostname_len > 0 && hostname_len < sizeof(peer_hostname)) { - memcpy(peer_hostname, payload + CRYPTO_PUBLIC_KEY_BYTES, hostname_len); - peer_hostname[hostname_len] = '\0'; /* Ensure null termination */ + size_t hostname_len = 0; + if (hdr->payload_size > CRYPTO_PUBLIC_KEY_BYTES) { + size_t max_len = hdr->payload_size - CRYPTO_PUBLIC_KEY_BYTES; + size_t i = 0; + for (; i < max_len; i++) { + if (payload[CRYPTO_PUBLIC_KEY_BYTES + i] == '\0') { + hostname_len = i; + break; + } + } + if (hostname_len > 0 && hostname_len < sizeof(peer_hostname)) { + memcpy(peer_hostname, payload + CRYPTO_PUBLIC_KEY_BYTES, hostname_len); + peer_hostname[hostname_len] = '\0'; /* Ensure null termination */ + } + } + + /* Optional protocol version + flags after hostname */ + size_t extensions_offset = CRYPTO_PUBLIC_KEY_BYTES; + if (hdr->payload_size > CRYPTO_PUBLIC_KEY_BYTES + hostname_len) { + extensions_offset = CRYPTO_PUBLIC_KEY_BYTES + hostname_len + 1; + } + uint8_t peer_version = PROTOCOL_VERSION; + uint8_t peer_flags = 0; + if (hdr->payload_size >= extensions_offset + 2) { + peer_version = payload[extensions_offset]; + peer_flags = payload[extensions_offset + 1]; + } + + if (peer_version < PROTOCOL_MIN_VERSION || peer_version > PROTOCOL_VERSION) { + fprintf(stderr, "WARNING: Peer protocol version %u unsupported\n", peer_version); + peer->state = PEER_DISCONNECTED; + return 0; } printf("✓ Received handshake from %s\n", peer_hostname[0] ? peer_hostname : "unknown"); @@ -444,6 +472,8 @@ int rootstream_net_recv(rootstream_ctx_t *ctx, int timeout_ms) { if (peer_hostname[0]) { snprintf(peer->hostname, sizeof(peer->hostname), "%s", peer_hostname); } + peer->protocol_version = peer_version; + peer->protocol_flags = peer_flags; /* Create encryption session (derive shared secret) */ if (crypto_create_session(&peer->session, ctx->keypair.secret_key, @@ -748,13 +778,18 @@ int rootstream_net_handshake(rootstream_ctx_t *ctx, peer_t *peer) { uint8_t payload[256]; memcpy(payload, ctx->keypair.public_key, CRYPTO_PUBLIC_KEY_BYTES); strcpy((char*)(payload + CRYPTO_PUBLIC_KEY_BYTES), ctx->keypair.identity); - + size_t payload_len = CRYPTO_PUBLIC_KEY_BYTES + strlen(ctx->keypair.identity) + 1; + if (payload_len + 2 <= sizeof(payload)) { + payload[payload_len] = PROTOCOL_VERSION; + payload[payload_len + 1] = PROTOCOL_FLAGS; + payload_len += 2; + } /* Send handshake (unencrypted for initial key exchange) */ packet_header_t hdr = {0}; hdr.magic = PACKET_MAGIC; - hdr.version = 1; + hdr.version = PROTOCOL_VERSION; hdr.type = PKT_HANDSHAKE; hdr.payload_size = payload_len; diff --git a/src/packet_validate.c b/src/packet_validate.c index 15038fc..97ea000 100644 --- a/src/packet_validate.c +++ b/src/packet_validate.c @@ -14,7 +14,7 @@ int rootstream_net_validate_packet(const uint8_t *buffer, size_t len) { return -1; } - if (hdr->version != 1) { + if (hdr->version < PROTOCOL_MIN_VERSION || hdr->version > PROTOCOL_VERSION) { return -1; }