Simplify frame header to message_type + payload_length (6 bytes)
Removes channel_id from the header. All message-specific identifiers (stream_id, request_id, etc.) now live at the start of the payload, interpreted by each message type handler. A relay seeing an unknown type can skip or forward it using only payload_length, with no knowledge of the payload structure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -126,20 +126,23 @@ Header fields:
|
|||||||
|
|
||||||
| Field | Size | Purpose |
|
| Field | Size | Purpose |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `message_type` | 2 bytes | Distinguishes video frame, control request, control response |
|
| `message_type` | 2 bytes | Determines how the payload is interpreted |
|
||||||
| `channel_id` | 2 bytes | For video: identifies the stream. For control: identifies the request/response pair (correlation ID) |
|
|
||||||
| `payload_length` | 4 bytes | Byte length of the following payload |
|
| `payload_length` | 4 bytes | Byte length of the following payload |
|
||||||
|
|
||||||
**Message types:**
|
The header is intentionally minimal. Any node — including a relay that does not recognise a message type — can skip or forward the frame by reading exactly `payload_length` bytes without needing to understand the payload. All message-specific identifiers (stream ID, correlation ID, etc.) live inside the payload and are handled by the relevant message type handler.
|
||||||
|
|
||||||
| Value | Meaning |
|
**Message types and their payload structure:**
|
||||||
|---|---|
|
|
||||||
| `0x0001` | Video frame |
|
|
||||||
| `0x0002` | Control request |
|
|
||||||
| `0x0003` | Control response |
|
|
||||||
| `0x0004` | Stream event |
|
|
||||||
|
|
||||||
Video frame payloads are raw compressed frames. Control payloads are binary-serialized structures — see [Protocol Serialization](#protocol-serialization). Stream events carry lifecycle signals for a channel — see [Device Resilience](#device-resilience).
|
| Value | Type | Payload starts with |
|
||||||
|
|---|---|---|
|
||||||
|
| `0x0001` | Video frame | `stream_id` (u16), then compressed frame data |
|
||||||
|
| `0x0002` | Control request | `request_id` (u16), then command-specific fields |
|
||||||
|
| `0x0003` | Control response | `request_id` (u16), then result-specific fields |
|
||||||
|
| `0x0004` | Stream event | `stream_id` (u16), `event_code` (u8), then event-specific fields |
|
||||||
|
|
||||||
|
Node-level messages (not tied to any stream or request) have no prefix beyond the header — the payload begins with the message-specific fields directly.
|
||||||
|
|
||||||
|
Control payloads are binary-serialized structures — see [Protocol Serialization](#protocol-serialization). Stream events carry lifecycle signals — see [Device Resilience](#device-resilience).
|
||||||
|
|
||||||
### Unified Control and Video on One Connection
|
### Unified Control and Video on One Connection
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ static void server_on_frame(struct Transport_Conn *conn,
|
|||||||
{
|
{
|
||||||
(void)userdata;
|
(void)userdata;
|
||||||
|
|
||||||
printf("recv type=0x%04x channel=%u length=%u",
|
printf("recv type=0x%04x length=%u",
|
||||||
frame->message_type, frame->channel_id, frame->payload_length);
|
frame->message_type, frame->payload_length);
|
||||||
|
|
||||||
if (frame->payload_length > 0) {
|
if (frame->payload_length > 0) {
|
||||||
uint32_t show = frame->payload_length < 8 ? frame->payload_length : 8;
|
uint32_t show = frame->payload_length < 8 ? frame->payload_length : 8;
|
||||||
@@ -28,7 +28,7 @@ static void server_on_frame(struct Transport_Conn *conn,
|
|||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
struct App_Error err = transport_send_frame(conn,
|
struct App_Error err = transport_send_frame(conn,
|
||||||
frame->message_type, frame->channel_id,
|
frame->message_type,
|
||||||
frame->payload, frame->payload_length);
|
frame->payload, frame->payload_length);
|
||||||
|
|
||||||
if (!APP_IS_OK(err)) {
|
if (!APP_IS_OK(err)) {
|
||||||
@@ -90,8 +90,8 @@ static void client_on_frame(struct Transport_Conn *conn,
|
|||||||
struct Transport_Frame *frame, void *userdata)
|
struct Transport_Frame *frame, void *userdata)
|
||||||
{
|
{
|
||||||
(void)conn; (void)userdata;
|
(void)conn; (void)userdata;
|
||||||
printf("echo type=0x%04x channel=%u length=%u\n",
|
printf("echo type=0x%04x length=%u\n",
|
||||||
frame->message_type, frame->channel_id, frame->payload_length);
|
frame->message_type, frame->payload_length);
|
||||||
free(frame->payload);
|
free(frame->payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,15 +123,15 @@ static void cmd_client(int argc, char **argv) {
|
|||||||
|
|
||||||
printf("connected to %s:%u\n", host, port);
|
printf("connected to %s:%u\n", host, port);
|
||||||
|
|
||||||
for (uint16_t i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
uint8_t payload[] = { 0xde, 0xad, 0xbe, 0xef, (uint8_t)i };
|
uint8_t payload[] = { 0xde, 0xad, 0xbe, 0xef, (uint8_t)i };
|
||||||
err = transport_send_frame(conn, 0x0001, i, payload, sizeof(payload));
|
err = transport_send_frame(conn, 0x0001, payload, sizeof(payload));
|
||||||
if (!APP_IS_OK(err)) {
|
if (!APP_IS_OK(err)) {
|
||||||
fprintf(stderr, "send failed on frame %u (errno %d)\n",
|
fprintf(stderr, "send failed on frame %d (errno %d)\n",
|
||||||
i, err.detail.syscall.err_no);
|
i, err.detail.syscall.err_no);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
printf("sent type=0x0001 channel=%u length=5\n", i);
|
printf("sent type=0x0001 length=5\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep(1);
|
sleep(1);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
|
|
||||||
#define TRANSPORT_FRAME_HEADER_SIZE 8u
|
#define TRANSPORT_FRAME_HEADER_SIZE 6u
|
||||||
#define TRANSPORT_DEFAULT_MAX_PAYLOAD (16u * 1024u * 1024u)
|
#define TRANSPORT_DEFAULT_MAX_PAYLOAD (16u * 1024u * 1024u)
|
||||||
|
|
||||||
struct Transport_Conn;
|
struct Transport_Conn;
|
||||||
@@ -13,10 +13,13 @@ struct Transport_Server;
|
|||||||
* A received frame. payload is malloc'd by the transport layer;
|
* A received frame. payload is malloc'd by the transport layer;
|
||||||
* the on_frame callback takes ownership and must free it.
|
* the on_frame callback takes ownership and must free it.
|
||||||
* payload is NULL when payload_length is 0.
|
* payload is NULL when payload_length is 0.
|
||||||
|
*
|
||||||
|
* The header carries only message_type and payload_length.
|
||||||
|
* All message-specific fields (stream_id, request_id, etc.) are
|
||||||
|
* the first bytes of the payload, interpreted by the message handler.
|
||||||
*/
|
*/
|
||||||
struct Transport_Frame {
|
struct Transport_Frame {
|
||||||
uint16_t message_type;
|
uint16_t message_type;
|
||||||
uint16_t channel_id;
|
|
||||||
uint32_t payload_length;
|
uint32_t payload_length;
|
||||||
uint8_t *payload;
|
uint8_t *payload;
|
||||||
};
|
};
|
||||||
@@ -77,7 +80,6 @@ struct App_Error transport_connect(struct Transport_Conn **out,
|
|||||||
*/
|
*/
|
||||||
struct App_Error transport_send_frame(struct Transport_Conn *conn,
|
struct App_Error transport_send_frame(struct Transport_Conn *conn,
|
||||||
uint16_t message_type,
|
uint16_t message_type,
|
||||||
uint16_t channel_id,
|
|
||||||
const uint8_t *payload,
|
const uint8_t *payload,
|
||||||
uint32_t length);
|
uint32_t length);
|
||||||
|
|
||||||
|
|||||||
@@ -61,8 +61,7 @@ static void *conn_read_thread_fn(void *arg) {
|
|||||||
|
|
||||||
struct Transport_Frame frame;
|
struct Transport_Frame frame;
|
||||||
frame.message_type = get_u16(header_buf, 0);
|
frame.message_type = get_u16(header_buf, 0);
|
||||||
frame.channel_id = get_u16(header_buf, 2);
|
frame.payload_length = get_u32(header_buf, 2);
|
||||||
frame.payload_length = get_u32(header_buf, 4);
|
|
||||||
|
|
||||||
if (frame.payload_length > conn->max_payload) {
|
if (frame.payload_length > conn->max_payload) {
|
||||||
break;
|
break;
|
||||||
@@ -287,13 +286,12 @@ struct App_Error transport_connect(struct Transport_Conn **out,
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct App_Error transport_send_frame(struct Transport_Conn *conn,
|
struct App_Error transport_send_frame(struct Transport_Conn *conn,
|
||||||
uint16_t message_type, uint16_t channel_id,
|
uint16_t message_type,
|
||||||
const uint8_t *payload, uint32_t length)
|
const uint8_t *payload, uint32_t length)
|
||||||
{
|
{
|
||||||
uint8_t header[TRANSPORT_FRAME_HEADER_SIZE];
|
uint8_t header[TRANSPORT_FRAME_HEADER_SIZE];
|
||||||
put_u16(header, 0, message_type);
|
put_u16(header, 0, message_type);
|
||||||
put_u16(header, 2, channel_id);
|
put_u32(header, 2, length);
|
||||||
put_u32(header, 4, length);
|
|
||||||
|
|
||||||
pthread_mutex_lock(&conn->write_mutex);
|
pthread_mutex_lock(&conn->write_mutex);
|
||||||
int ok = write_exact(conn->fd, header, TRANSPORT_FRAME_HEADER_SIZE);
|
int ok = write_exact(conn->fd, header, TRANSPORT_FRAME_HEADER_SIZE);
|
||||||
|
|||||||
Reference in New Issue
Block a user