Add protocol module, video-node binary, query/web CLI tools
- Protocol module: framed binary encoding for control requests/responses (ENUM_DEVICES, ENUM_CONTROLS, GET/SET_CONTROL, STREAM_OPEN/CLOSE) - video-node: scans /dev/media* and /dev/video*, serves V4L2 device topology and controls over TCP; uses UDP discovery for peer announce - query_cli: auto-discovers a node, queries devices and controls - protocol_cli: low-level protocol frame decoder for debugging - dev/web: Express 5 ESM web inspector — live SSE discovery picker, REST bridge to video-node, controls UI with sliders/selects/checkboxes - Makefile: sequential module builds before cli/node to fix make -j races - common.mk: add DEPFLAGS (-MMD -MP) for automatic header dependencies - All module Makefiles: split compile/link, generate .d dependency files - discovery: replace 100ms poll loop with pthread_cond_timedwait; respond to all announcements (not just new peers) for instant re-discovery - ENUM_DEVICES response: carry device_caps (V4L2_CAP_*) per video node so clients can distinguish capture nodes from metadata nodes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
706
src/modules/protocol/protocol.c
Normal file
706
src/modules/protocol/protocol.c
Normal file
@@ -0,0 +1,706 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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_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_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)
|
||||
{
|
||||
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 = 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_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 *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); }
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user