#include #include #include "protocol.h" #include "serial.h" /* -- growable write buffer ------------------------------------------------- */ struct Wbuf { uint8_t *data; uint32_t len; uint32_t cap; }; static struct App_Error wbuf_init(struct Wbuf *b, uint32_t initial) { b->data = malloc(initial); if (!b->data) { return APP_SYSCALL_ERROR(); } b->len = 0; b->cap = initial; return APP_OK; } static void wbuf_free(struct Wbuf *b) { free(b->data); } static struct App_Error wbuf_grow(struct Wbuf *b, uint32_t extra) { if (b->len + extra <= b->cap) { return APP_OK; } uint32_t newcap = b->cap ? b->cap : 64; while (newcap < b->len + extra) { newcap *= 2; } uint8_t *p = realloc(b->data, newcap); if (!p) { return APP_SYSCALL_ERROR(); } b->data = p; b->cap = newcap; return APP_OK; } static struct App_Error wbuf_u8(struct Wbuf *b, uint8_t v) { struct App_Error e = wbuf_grow(b, 1); if (!APP_IS_OK(e)) { return e; } put_u8(b->data, b->len, v); b->len += 1; return APP_OK; } static struct App_Error wbuf_u16(struct Wbuf *b, uint16_t v) { struct App_Error e = wbuf_grow(b, 2); if (!APP_IS_OK(e)) { return e; } put_u16(b->data, b->len, v); b->len += 2; return APP_OK; } static struct App_Error wbuf_i16(struct Wbuf *b, int16_t v) { struct App_Error e = wbuf_grow(b, 2); if (!APP_IS_OK(e)) { return e; } put_i16(b->data, b->len, v); b->len += 2; return APP_OK; } static struct App_Error wbuf_u32(struct Wbuf *b, uint32_t v) { struct App_Error e = wbuf_grow(b, 4); if (!APP_IS_OK(e)) { return e; } put_u32(b->data, b->len, v); b->len += 4; return APP_OK; } static struct App_Error wbuf_i32(struct Wbuf *b, int32_t v) { struct App_Error e = wbuf_grow(b, 4); if (!APP_IS_OK(e)) { return e; } put_i32(b->data, b->len, v); b->len += 4; return APP_OK; } static struct App_Error wbuf_i64(struct Wbuf *b, int64_t v) { struct App_Error e = wbuf_grow(b, 8); if (!APP_IS_OK(e)) { return e; } put_i64(b->data, b->len, v); b->len += 8; return APP_OK; } /* Serialise a string as u8 length prefix + bytes (truncates at 255). */ static struct App_Error wbuf_str8(struct Wbuf *b, const char *s) { size_t slen = s ? strlen(s) : 0; uint8_t n = (slen > 255u) ? 255u : (uint8_t)slen; struct App_Error e = wbuf_grow(b, (uint32_t)1 + n); if (!APP_IS_OK(e)) { return e; } put_u8(b->data, b->len, n); b->len += 1; memcpy(b->data + b->len, s, n); b->len += n; return APP_OK; } static struct App_Error wbuf_bytes(struct Wbuf *b, const uint8_t *data, uint32_t len) { struct App_Error e = wbuf_grow(b, len); if (!APP_IS_OK(e)) { return e; } memcpy(b->data + b->len, data, len); b->len += len; return APP_OK; } /* -- sequential read cursor ------------------------------------------------ */ struct Cursor { const uint8_t *buf; uint32_t len; uint32_t pos; int ok; }; static void cur_init(struct Cursor *c, const uint8_t *buf, uint32_t len) { c->buf = buf; c->len = len; c->pos = 0; c->ok = 1; } static void cur_need(struct Cursor *c, uint32_t n) { if (c->ok && c->pos + n > c->len) { c->ok = 0; } } static uint8_t cur_u8(struct Cursor *c) { cur_need(c, 1); if (!c->ok) { return 0; } uint8_t v = get_u8(c->buf, c->pos); c->pos += 1; return v; } static uint16_t cur_u16(struct Cursor *c) { cur_need(c, 2); if (!c->ok) { return 0; } uint16_t v = get_u16(c->buf, c->pos); c->pos += 2; return v; } static uint32_t cur_u32(struct Cursor *c) { cur_need(c, 4); if (!c->ok) { return 0; } uint32_t v = get_u32(c->buf, c->pos); c->pos += 4; return v; } static int32_t cur_i32(struct Cursor *c) { cur_need(c, 4); if (!c->ok) { return 0; } int32_t v = get_i32(c->buf, c->pos); c->pos += 4; return v; } static int64_t cur_i64(struct Cursor *c) { cur_need(c, 8); if (!c->ok) { return 0; } int64_t v = get_i64(c->buf, c->pos); c->pos += 8; return v; } /* Read a u8-length-prefixed string; returns pointer into buf and sets *len. */ static const char *cur_str8(struct Cursor *c, uint8_t *len_out) { uint8_t n = cur_u8(c); cur_need(c, n); if (!c->ok) { *len_out = 0; return NULL; } const char *p = (const char *)(c->buf + c->pos); c->pos += n; *len_out = n; return p; } #define CUR_CHECK(c) \ do { if (!(c).ok) { return APP_INVALID_ERROR_MSG(0, "payload too short"); } } while (0) /* -- write helpers --------------------------------------------------------- */ /* * Build a CONTROL_RESPONSE base (request_id + status) into b, then append * extra, then send — all in one call. Used by the specific response writers. */ static struct App_Error send_response(struct Transport_Conn *conn, uint16_t request_id, uint16_t status, const uint8_t *extra, uint32_t extra_len) { struct Wbuf b; struct App_Error e = wbuf_init(&b, 4 + extra_len); if (!APP_IS_OK(e)) { return e; } e = wbuf_u16(&b, request_id); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u16(&b, status); if (!APP_IS_OK(e)) { goto fail; } if (extra && extra_len > 0) { e = wbuf_bytes(&b, extra, extra_len); if (!APP_IS_OK(e)) { goto fail; } } e = transport_send_frame(conn, PROTO_MSG_CONTROL_RESPONSE, b.data, b.len); fail: wbuf_free(&b); return e; } /* -- write functions ------------------------------------------------------- */ struct App_Error proto_write_video_frame(struct Transport_Conn *conn, uint16_t stream_id, const uint8_t *data, uint32_t data_len) { uint32_t total = 2u + data_len; uint8_t *buf = malloc(total); if (!buf) { return APP_SYSCALL_ERROR(); } put_u16(buf, 0, stream_id); memcpy(buf + 2, data, data_len); struct App_Error e = transport_send_frame(conn, PROTO_MSG_VIDEO_FRAME, buf, total); free(buf); return e; } struct App_Error proto_write_stream_event(struct Transport_Conn *conn, uint16_t stream_id, uint8_t event_code) { uint8_t buf[3]; put_u16(buf, 0, stream_id); put_u8 (buf, 2, event_code); return transport_send_frame(conn, PROTO_MSG_STREAM_EVENT, buf, 3); } struct App_Error proto_write_stream_open(struct Transport_Conn *conn, uint16_t request_id, uint16_t stream_id, uint16_t format, uint16_t pixel_format, uint16_t origin) { uint8_t buf[12]; put_u16(buf, 0, request_id); put_u16(buf, 2, PROTO_CMD_STREAM_OPEN); put_u16(buf, 4, stream_id); put_u16(buf, 6, format); put_u16(buf, 8, pixel_format); put_u16(buf, 10, origin); return transport_send_frame(conn, PROTO_MSG_CONTROL_REQUEST, buf, 12); } struct App_Error proto_write_stream_close(struct Transport_Conn *conn, uint16_t request_id, uint16_t stream_id) { uint8_t buf[6]; put_u16(buf, 0, request_id); put_u16(buf, 2, PROTO_CMD_STREAM_CLOSE); put_u16(buf, 4, stream_id); return transport_send_frame(conn, PROTO_MSG_CONTROL_REQUEST, buf, 6); } struct App_Error proto_write_enum_devices(struct Transport_Conn *conn, uint16_t request_id) { uint8_t buf[4]; put_u16(buf, 0, request_id); put_u16(buf, 2, PROTO_CMD_ENUM_DEVICES); return transport_send_frame(conn, PROTO_MSG_CONTROL_REQUEST, buf, 4); } struct App_Error proto_write_enum_controls(struct Transport_Conn *conn, uint16_t request_id, uint16_t device_index) { uint8_t buf[6]; put_u16(buf, 0, request_id); put_u16(buf, 2, PROTO_CMD_ENUM_CONTROLS); put_u16(buf, 4, device_index); return transport_send_frame(conn, PROTO_MSG_CONTROL_REQUEST, buf, 6); } struct App_Error proto_write_get_control(struct Transport_Conn *conn, uint16_t request_id, uint16_t device_index, uint32_t control_id) { uint8_t buf[10]; put_u16(buf, 0, request_id); put_u16(buf, 2, PROTO_CMD_GET_CONTROL); put_u16(buf, 4, device_index); put_u32(buf, 6, control_id); return transport_send_frame(conn, PROTO_MSG_CONTROL_REQUEST, buf, 10); } struct App_Error proto_write_set_control(struct Transport_Conn *conn, uint16_t request_id, uint16_t device_index, uint32_t control_id, int32_t value) { uint8_t buf[14]; put_u16(buf, 0, request_id); put_u16(buf, 2, PROTO_CMD_SET_CONTROL); put_u16(buf, 4, device_index); put_u32(buf, 6, control_id); put_i32(buf, 10, value); return transport_send_frame(conn, PROTO_MSG_CONTROL_REQUEST, buf, 14); } struct App_Error proto_write_enum_monitors(struct Transport_Conn *conn, uint16_t request_id) { uint8_t buf[4]; put_u16(buf, 0, request_id); put_u16(buf, 2, PROTO_CMD_ENUM_MONITORS); return transport_send_frame(conn, PROTO_MSG_CONTROL_REQUEST, buf, 4); } struct App_Error proto_write_start_ingest(struct Transport_Conn *conn, uint16_t request_id, uint16_t stream_id, uint16_t format, uint16_t width, uint16_t height, uint16_t fps_n, uint16_t fps_d, uint16_t transport_mode, const char *device_path, const char *dest_host, uint16_t dest_port) { size_t dp_len = device_path ? strlen(device_path) : 0; size_t dh_len = dest_host ? strlen(dest_host) : 0; uint8_t dp_n = dp_len > 255u ? 255u : (uint8_t)dp_len; uint8_t dh_n = dh_len > 255u ? 255u : (uint8_t)dh_len; /* 20 bytes fixed + 1+dp_n (device_path str8) + 1+dh_n (dest_host str8) */ uint32_t total = 20u + 1u + dp_n + 1u + dh_n; uint8_t *buf = malloc(total); if (!buf) { return APP_SYSCALL_ERROR(); } uint32_t o = 0; put_u16(buf, o, request_id); o += 2; put_u16(buf, o, PROTO_CMD_START_INGEST); o += 2; put_u16(buf, o, stream_id); o += 2; put_u16(buf, o, format); o += 2; put_u16(buf, o, width); o += 2; put_u16(buf, o, height); o += 2; put_u16(buf, o, fps_n); o += 2; put_u16(buf, o, fps_d); o += 2; put_u16(buf, o, dest_port); o += 2; put_u16(buf, o, transport_mode); o += 2; put_u8 (buf, o, dp_n); o += 1; memcpy(buf + o, device_path, dp_n); o += dp_n; put_u8 (buf, o, dh_n); o += 1; memcpy(buf + o, dest_host, dh_n); o += dh_n; struct App_Error e = transport_send_frame(conn, PROTO_MSG_CONTROL_REQUEST, buf, total); free(buf); return e; } struct App_Error proto_write_stop_ingest(struct Transport_Conn *conn, uint16_t request_id, uint16_t stream_id) { uint8_t buf[6]; put_u16(buf, 0, request_id); put_u16(buf, 2, PROTO_CMD_STOP_INGEST); put_u16(buf, 4, stream_id); return transport_send_frame(conn, PROTO_MSG_CONTROL_REQUEST, buf, 6); } struct App_Error proto_write_control_response(struct Transport_Conn *conn, uint16_t request_id, uint16_t status, const uint8_t *payload, uint32_t payload_len) { return send_response(conn, request_id, status, payload, payload_len); } struct App_Error proto_write_get_control_response(struct Transport_Conn *conn, uint16_t request_id, uint16_t status, int32_t value) { uint8_t extra[4]; put_i32(extra, 0, value); return send_response(conn, request_id, status, extra, 4); } struct App_Error proto_write_enum_devices_response(struct Transport_Conn *conn, uint16_t request_id, uint16_t status, const struct Proto_Media_Device_Info *media_devices, uint16_t media_count, const struct Proto_Standalone_Device_Info *standalone, uint16_t standalone_count, const struct Proto_Display_Device_Info *displays, uint16_t display_count) { struct Wbuf b; struct App_Error e = wbuf_init(&b, 128); if (!APP_IS_OK(e)) { return e; } e = wbuf_u16(&b, request_id); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u16(&b, status); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u16(&b, media_count); if (!APP_IS_OK(e)) { goto fail; } for (uint16_t i = 0; i < media_count; i++) { const struct Proto_Media_Device_Info *m = &media_devices[i]; e = wbuf_str8(&b, m->path); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_str8(&b, m->driver); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_str8(&b, m->model); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_str8(&b, m->bus_info); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u8 (&b, m->video_node_count); if (!APP_IS_OK(e)) { goto fail; } for (uint8_t j = 0; j < m->video_node_count; j++) { const struct Proto_Video_Node_Info *v = &m->video_nodes[j]; e = wbuf_str8(&b, v->path); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_str8(&b, v->entity_name); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u32 (&b, v->entity_type); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u32 (&b, v->entity_flags);if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u32 (&b, v->device_caps); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u8 (&b, v->pad_flags); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u8 (&b, v->is_capture); if (!APP_IS_OK(e)) { goto fail; } } } e = wbuf_u16(&b, standalone_count); if (!APP_IS_OK(e)) { goto fail; } for (uint16_t i = 0; i < standalone_count; i++) { e = wbuf_str8(&b, standalone[i].path); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_str8(&b, standalone[i].name); if (!APP_IS_OK(e)) { goto fail; } } e = wbuf_u16(&b, display_count); if (!APP_IS_OK(e)) { goto fail; } for (uint16_t i = 0; i < display_count; i++) { const struct Proto_Display_Device_Info *d = &displays[i]; e = wbuf_u16(&b, d->device_id); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u16(&b, d->stream_id); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_i16(&b, d->win_x); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_i16(&b, d->win_y); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u16(&b, d->win_w); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u16(&b, d->win_h); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u8 (&b, d->scale_mode); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u8 (&b, d->anchor); if (!APP_IS_OK(e)) { goto fail; } } e = transport_send_frame(conn, PROTO_MSG_CONTROL_RESPONSE, b.data, b.len); fail: wbuf_free(&b); return e; } struct App_Error proto_write_enum_controls_response(struct Transport_Conn *conn, uint16_t request_id, uint16_t status, const struct Proto_Control_Info *controls, uint16_t count) { struct Wbuf b; struct App_Error e = wbuf_init(&b, 256); if (!APP_IS_OK(e)) { return e; } e = wbuf_u16(&b, request_id); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u16(&b, status); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u16(&b, count); if (!APP_IS_OK(e)) { goto fail; } for (uint16_t i = 0; i < count; i++) { const struct Proto_Control_Info *c = &controls[i]; e = wbuf_u32(&b, c->id); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u8 (&b, c->type); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u32(&b, c->flags); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_str8(&b, c->name); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_i32(&b, c->min); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_i32(&b, c->max); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_i32(&b, c->step); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_i32(&b, c->default_val); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_i32(&b, c->current_val); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u8 (&b, c->menu_count); if (!APP_IS_OK(e)) { goto fail; } for (uint8_t j = 0; j < c->menu_count; j++) { const struct Proto_Menu_Item *m = &c->menu_items[j]; e = wbuf_u32(&b, m->index); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_str8(&b, m->name); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_i64(&b, m->int_value); if (!APP_IS_OK(e)) { goto fail; } } } e = transport_send_frame(conn, PROTO_MSG_CONTROL_RESPONSE, b.data, b.len); fail: wbuf_free(&b); return e; } struct App_Error proto_write_enum_monitors_response(struct Transport_Conn *conn, uint16_t request_id, uint16_t status, const struct Proto_Monitor_Info *monitors, uint16_t count) { struct Wbuf b; struct App_Error e = wbuf_init(&b, 64); if (!APP_IS_OK(e)) { return e; } e = wbuf_u16(&b, request_id); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u16(&b, status); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u16(&b, count); if (!APP_IS_OK(e)) { goto fail; } for (uint16_t i = 0; i < count; i++) { const struct Proto_Monitor_Info *m = &monitors[i]; e = wbuf_i32(&b, m->x); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_i32(&b, m->y); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u32(&b, m->width); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_u32(&b, m->height); if (!APP_IS_OK(e)) { goto fail; } e = wbuf_str8(&b, m->name); if (!APP_IS_OK(e)) { goto fail; } } e = transport_send_frame(conn, PROTO_MSG_CONTROL_RESPONSE, b.data, b.len); fail: wbuf_free(&b); return e; } /* -- read functions -------------------------------------------------------- */ struct App_Error proto_read_video_frame( const uint8_t *payload, uint32_t length, struct Proto_Video_Frame *out) { if (length < 2) { return APP_INVALID_ERROR_MSG(0, "VIDEO_FRAME payload too short"); } out->stream_id = get_u16(payload, 0); out->data = payload + 2; out->data_len = length - 2; return APP_OK; } struct App_Error proto_read_stream_event( const uint8_t *payload, uint32_t length, struct Proto_Stream_Event *out) { if (length < 3) { return APP_INVALID_ERROR_MSG(0, "STREAM_EVENT payload too short"); } out->stream_id = get_u16(payload, 0); out->event_code = get_u8 (payload, 2); return APP_OK; } struct App_Error proto_read_request_header( const uint8_t *payload, uint32_t length, struct Proto_Request_Header *out) { if (length < 4) { return APP_INVALID_ERROR_MSG(0, "CONTROL_REQUEST payload too short"); } out->request_id = get_u16(payload, 0); out->command = get_u16(payload, 2); return APP_OK; } struct App_Error proto_read_stream_open( const uint8_t *payload, uint32_t length, struct Proto_Stream_Open *out) { if (length < 12) { return APP_INVALID_ERROR_MSG(0, "STREAM_OPEN payload too short"); } out->request_id = get_u16(payload, 0); out->stream_id = get_u16(payload, 4); out->format = get_u16(payload, 6); out->pixel_format = get_u16(payload, 8); out->origin = get_u16(payload, 10); return APP_OK; } struct App_Error proto_read_stream_close( const uint8_t *payload, uint32_t length, struct Proto_Stream_Close *out) { if (length < 6) { return APP_INVALID_ERROR_MSG(0, "STREAM_CLOSE payload too short"); } out->request_id = get_u16(payload, 0); out->stream_id = get_u16(payload, 4); return APP_OK; } struct App_Error proto_read_enum_controls_req( const uint8_t *payload, uint32_t length, struct Proto_Enum_Controls_Req *out) { if (length < 6) { return APP_INVALID_ERROR_MSG(0, "ENUM_CONTROLS request payload too short"); } out->request_id = get_u16(payload, 0); out->device_index = get_u16(payload, 4); return APP_OK; } struct App_Error proto_read_get_control_req( const uint8_t *payload, uint32_t length, struct Proto_Get_Control_Req *out) { if (length < 10) { return APP_INVALID_ERROR_MSG(0, "GET_CONTROL request payload too short"); } out->request_id = get_u16(payload, 0); out->device_index = get_u16(payload, 4); out->control_id = get_u32(payload, 6); return APP_OK; } struct App_Error proto_read_set_control_req( const uint8_t *payload, uint32_t length, struct Proto_Set_Control_Req *out) { if (length < 14) { return APP_INVALID_ERROR_MSG(0, "SET_CONTROL request payload too short"); } out->request_id = get_u16(payload, 0); out->device_index = get_u16(payload, 4); out->control_id = get_u32(payload, 6); out->value = get_i32(payload, 10); return APP_OK; } struct App_Error proto_read_start_ingest( const uint8_t *payload, uint32_t length, struct Proto_Start_Ingest *out) { /* Fixed portion: request_id(2) cmd(2) stream_id(2) format(2) width(2) * height(2) fps_n(2) fps_d(2) dest_port(2) transport_mode(2) = 20 bytes, * then two str8 fields. */ struct Cursor c; cur_init(&c, payload, length); out->request_id = cur_u16(&c); /* skip command word at [2..3] */ (void) cur_u16(&c); out->stream_id = cur_u16(&c); out->format = cur_u16(&c); out->width = cur_u16(&c); out->height = cur_u16(&c); out->fps_n = cur_u16(&c); out->fps_d = cur_u16(&c); out->dest_port = cur_u16(&c); out->transport_mode = cur_u16(&c); out->device_path = cur_str8(&c, &out->device_path_len); out->dest_host = cur_str8(&c, &out->dest_host_len); CUR_CHECK(c); return APP_OK; } struct App_Error proto_read_stop_ingest( const uint8_t *payload, uint32_t length, struct Proto_Stop_Ingest *out) { if (length < 6) { return APP_INVALID_ERROR_MSG(0, "STOP_INGEST payload too short"); } out->request_id = get_u16(payload, 0); out->stream_id = get_u16(payload, 4); return APP_OK; } /* START_DISPLAY: request_id(2) cmd(2) stream_id(2) win_x(2) win_y(2) * win_w(2) win_h(2) scale_mode(1) anchor(1) no_signal_fps(1) reserved(1) = 18 bytes */ struct App_Error proto_write_start_display(struct Transport_Conn *conn, uint16_t request_id, uint16_t stream_id, int16_t win_x, int16_t win_y, uint16_t win_w, uint16_t win_h, uint8_t scale_mode, uint8_t anchor, uint8_t no_signal_fps) { uint8_t buf[18]; uint32_t o = 0; put_u16(buf, o, request_id); o += 2; put_u16(buf, o, PROTO_CMD_START_DISPLAY); o += 2; put_u16(buf, o, stream_id); o += 2; put_i16(buf, o, win_x); o += 2; put_i16(buf, o, win_y); o += 2; put_u16(buf, o, win_w); o += 2; put_u16(buf, o, win_h); o += 2; put_u8 (buf, o, scale_mode); o += 1; put_u8 (buf, o, anchor); o += 1; put_u8 (buf, o, no_signal_fps); o += 1; put_u8 (buf, o, 0); o += 1; /* reserved */ (void)o; return transport_send_frame(conn, PROTO_MSG_CONTROL_REQUEST, buf, 18); } struct App_Error proto_write_stop_display(struct Transport_Conn *conn, uint16_t request_id, uint16_t stream_id) { uint8_t buf[6]; put_u16(buf, 0, request_id); put_u16(buf, 2, PROTO_CMD_STOP_DISPLAY); put_u16(buf, 4, stream_id); return transport_send_frame(conn, PROTO_MSG_CONTROL_REQUEST, buf, 6); } struct App_Error proto_read_start_display( const uint8_t *payload, uint32_t length, struct Proto_Start_Display *out) { if (length < 16) { return APP_INVALID_ERROR_MSG(0, "START_DISPLAY payload too short"); } out->request_id = get_u16(payload, 0); /* skip command word at [2..3] */ out->stream_id = get_u16(payload, 4); out->win_x = get_i16(payload, 6); out->win_y = get_i16(payload, 8); out->win_w = get_u16(payload, 10); out->win_h = get_u16(payload, 12); out->scale_mode = get_u8 (payload, 14); out->anchor = get_u8 (payload, 15); out->no_signal_fps = length >= 18 ? get_u8(payload, 16) : 0; return APP_OK; } struct App_Error proto_read_stop_display( const uint8_t *payload, uint32_t length, struct Proto_Stop_Display *out) { if (length < 6) { return APP_INVALID_ERROR_MSG(0, "STOP_DISPLAY payload too short"); } out->request_id = get_u16(payload, 0); out->stream_id = get_u16(payload, 4); return APP_OK; } struct App_Error proto_read_response_header( const uint8_t *payload, uint32_t length, struct Proto_Response_Header *out) { if (length < 4) { return APP_INVALID_ERROR_MSG(0, "CONTROL_RESPONSE payload too short"); } out->request_id = get_u16(payload, 0); out->status = get_u16(payload, 2); return APP_OK; } struct App_Error proto_read_get_control_response( const uint8_t *payload, uint32_t length, struct Proto_Get_Control_Resp *out) { if (length < 8) { return APP_INVALID_ERROR_MSG(0, "GET_CONTROL response payload too short"); } out->request_id = get_u16(payload, 0); out->status = get_u16(payload, 2); out->value = get_i32(payload, 4); return APP_OK; } struct App_Error proto_read_enum_devices_response( const uint8_t *payload, uint32_t length, struct Proto_Response_Header *header_out, void (*on_media_device)( const char *path, uint8_t path_len, const char *driver, uint8_t driver_len, const char *model, uint8_t model_len, const char *bus_info, uint8_t bus_info_len, uint8_t video_node_count, void *userdata), void (*on_video_node)( const char *path, uint8_t path_len, const char *entity_name, uint8_t entity_name_len, uint32_t entity_type, uint32_t entity_flags, uint32_t device_caps, uint8_t pad_flags, uint8_t is_capture, void *userdata), void (*on_standalone)( const char *path, uint8_t path_len, const char *name, uint8_t name_len, void *userdata), void (*on_display)( uint16_t stream_id, int16_t win_x, int16_t win_y, uint16_t win_w, uint16_t win_h, uint8_t scale_mode, uint8_t anchor, void *userdata), void *userdata) { struct Cursor c; cur_init(&c, payload, length); header_out->request_id = cur_u16(&c); header_out->status = cur_u16(&c); uint16_t media_count = cur_u16(&c); CUR_CHECK(c); for (uint16_t i = 0; i < media_count; i++) { uint8_t path_len, driver_len, model_len, bus_info_len; const char *path = cur_str8(&c, &path_len); const char *driver = cur_str8(&c, &driver_len); const char *model = cur_str8(&c, &model_len); const char *bus_info = cur_str8(&c, &bus_info_len); uint8_t vcount = cur_u8(&c); CUR_CHECK(c); if (on_media_device) { on_media_device(path, path_len, driver, driver_len, model, model_len, bus_info, bus_info_len, vcount, userdata); } for (uint8_t j = 0; j < vcount; j++) { uint8_t vpath_len, ename_len; const char *vpath = cur_str8(&c, &vpath_len); const char *ename = cur_str8(&c, &ename_len); uint32_t etype = cur_u32(&c); uint32_t eflags = cur_u32(&c); uint32_t dcaps = cur_u32(&c); uint8_t pflags = cur_u8(&c); uint8_t iscap = cur_u8(&c); CUR_CHECK(c); if (on_video_node) { on_video_node(vpath, vpath_len, ename, ename_len, etype, eflags, dcaps, pflags, iscap, userdata); } } } uint16_t standalone_count = cur_u16(&c); CUR_CHECK(c); for (uint16_t i = 0; i < standalone_count; i++) { uint8_t path_len, name_len; const char *path = cur_str8(&c, &path_len); const char *name = cur_str8(&c, &name_len); CUR_CHECK(c); if (on_standalone) { on_standalone(path, path_len, name, name_len, userdata); } } /* Display section — optional; absent in messages from older nodes */ if (c.ok && c.pos + 2 <= c.len) { uint16_t display_count = cur_u16(&c); CUR_CHECK(c); for (uint16_t i = 0; i < display_count; i++) { uint16_t device_id = cur_u16(&c); uint16_t stream_id = cur_u16(&c); int16_t win_x = (int16_t)cur_u16(&c); int16_t win_y = (int16_t)cur_u16(&c); uint16_t win_w = cur_u16(&c); uint16_t win_h = cur_u16(&c); uint8_t scale_mode = cur_u8(&c); uint8_t anchor = cur_u8(&c); CUR_CHECK(c); if (on_display) { on_display(device_id, stream_id, win_x, win_y, win_w, win_h, scale_mode, anchor, userdata); } } } return APP_OK; } struct App_Error proto_read_enum_controls_response( const uint8_t *payload, uint32_t length, struct Proto_Response_Header *header_out, void (*on_control)( uint32_t id, uint8_t type, uint32_t flags, const char *name, uint8_t name_len, int32_t min, int32_t max, int32_t step, int32_t default_val, int32_t current_val, uint8_t menu_count, void *userdata), void (*on_menu_item)( uint32_t index, const char *name, uint8_t name_len, int64_t int_value, void *userdata), void *userdata) { struct Cursor c; cur_init(&c, payload, length); header_out->request_id = cur_u16(&c); header_out->status = cur_u16(&c); CUR_CHECK(c); if (header_out->status != PROTO_STATUS_OK) { return APP_OK; } uint16_t count = cur_u16(&c); CUR_CHECK(c); for (uint16_t i = 0; i < count; i++) { uint32_t id = cur_u32(&c); uint8_t type = cur_u8 (&c); uint32_t flags = cur_u32(&c); uint8_t name_len; const char *name = cur_str8(&c, &name_len); int32_t min = cur_i32(&c); int32_t max = cur_i32(&c); int32_t step = cur_i32(&c); int32_t def = cur_i32(&c); int32_t cur = cur_i32(&c); uint8_t menu_count = cur_u8(&c); CUR_CHECK(c); if (on_control) { on_control(id, type, flags, name, name_len, min, max, step, def, cur, menu_count, userdata); } for (uint8_t j = 0; j < menu_count; j++) { uint32_t midx = cur_u32(&c); uint8_t mname_len; const char *mname = cur_str8(&c, &mname_len); int64_t mval = cur_i64(&c); CUR_CHECK(c); if (on_menu_item) { on_menu_item(midx, mname, mname_len, mval, userdata); } } } return APP_OK; } struct App_Error proto_read_enum_monitors_response( const uint8_t *payload, uint32_t length, struct Proto_Response_Header *header_out, void (*on_monitor)( int32_t x, int32_t y, uint32_t width, uint32_t height, const char *name, uint8_t name_len, void *userdata), void *userdata) { struct Cursor c; cur_init(&c, payload, length); header_out->request_id = cur_u16(&c); header_out->status = cur_u16(&c); uint16_t count = cur_u16(&c); CUR_CHECK(c); for (uint16_t i = 0; i < count; i++) { int32_t x = cur_i32(&c); int32_t y = cur_i32(&c); uint32_t width = cur_u32(&c); uint32_t height = cur_u32(&c); uint8_t name_len; const char *name = cur_str8(&c, &name_len); CUR_CHECK(c); if (on_monitor) { on_monitor(x, y, width, height, name, name_len, userdata); } } return APP_OK; }