jitterbug/wire.c
2023-01-01 05:22:37 +02:00

438 lines
14 KiB
C

#include <string.h>
#include <stdio.h>
#include "try.h"
#include "wire.h"
#define WIRE_ENABLE_VERBOSE 1
#if WIRE_ENABLE_VERBOSE
#define WIRE_VERBOSE printf
#else
#define WIRE_VERBOSE(...)
#endif
bool wire_align(wire_context_t *c, uint32_t alignment)
{
if ((c->byte_cursor % alignment) == 0) {
return true;
}
uint32_t new_cursor = c->byte_cursor + alignment - (c->byte_cursor % alignment);
if (new_cursor >= c->data_len || new_cursor < c->byte_cursor) {
return false;
}
c->byte_cursor = new_cursor;
return true;
}
bool wire_write_align(wire_context_t *c, uint32_t alignment)
{
if ((c->byte_cursor % alignment) == 0) {
return true;
}
uint32_t new_cursor = c->byte_cursor + alignment - (c->byte_cursor % alignment);
if (new_cursor >= c->data_len || new_cursor < c->byte_cursor) {
return false;
}
while (c->byte_cursor < new_cursor) {
c->data[c->byte_cursor] = '\0';
c->byte_cursor++;
}
return true;
}
// SPEC: https://dbus.freedesktop.org/doc/dbus-specification.html#id-1.4.6
_WIRE_DEF_BYTE_TYPE(i8, int8_t);
_WIRE_DEF_BYTE_TYPE(u8, uint8_t);
_WIRE_DEF_TYPE(i16, int16_t, 2);
_WIRE_DEF_TYPE(u16, uint16_t, 2);
_WIRE_DEF_TYPE(i32, int32_t, 4);
_WIRE_DEF_TYPE(u32, uint32_t, 4);
_WIRE_DEF_TYPE(boolean, uint32_t, 4);
char *wire_get_string_impl(wire_context_t *c, bool as_signature)
{
uint32_t len = 0;
if (as_signature) {
len = *(uint8_t*)TRYPTR_NIL(wire_get_u8(c));
} else {
len = *(uint32_t*)TRYPTR_NIL(wire_get_u32(c));
}
uint32_t new_cursor = c->byte_cursor + len + 1; /* + 1 because of the extra null termination that strings have */
if (new_cursor >= c->data_len) {
return NULL;
}
// strings always end with a null byte
if (c->data[c->byte_cursor + len] != '\0') {
return NULL;
}
char *str = (char *)&c->data[c->byte_cursor];
if (strlen(str) != len) {
return NULL;
}
c->byte_cursor = new_cursor;
return str;
}
char *wire_set_string_impl(wire_context_t *c, const char *str, bool as_signature)
{
uint32_t len = strlen(str);
if (as_signature) {
TRYPTR_NIL(wire_set_u8(c, len));
} else {
TRYPTR_NIL(wire_set_u32(c, len));
}
uint32_t new_cursor = c->byte_cursor + len + 1; /* + 1 because of the extra null termination that strings have */
if (new_cursor >= c->data_len) {
return NULL;
}
// TODO?
char *dest = (char*)&c->data[c->byte_cursor];
memcpy(dest, str, len + 1);
c->byte_cursor = new_cursor;
return dest;
}
int wire_parse_message(wire_context_t *c, wire_message_t *msg)
{
// SPEC: https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-messages
msg->endianness = *(uint8_t*)TRYPTR(wire_get_u8(c));
if (msg->endianness == 'B') {
// we only support little endian
return -1;
}
msg->type = *(uint8_t*)TRYPTR(wire_get_u8(c));
msg->flags = *(uint8_t*)TRYPTR(wire_get_u8(c));
msg->protocol_version = *(uint8_t*)TRYPTR(wire_get_u8(c));
msg->body_length = *(uint32_t*)TRYPTR(wire_get_u32(c));
msg->serial = *(uint32_t*)TRYPTR(wire_get_u32(c));
msg->header_fields_length = *(uint32_t*)TRYPTR(wire_get_u32(c));
size_t header_fields_end = c->byte_cursor + msg->header_fields_length;
WIRE_VERBOSE("start parse message: type=%d, no_reply_expected=%d\n", msg->type, msg->flags & DBUS_FLAG_NO_REPLY_EXPECTED);
while (c->byte_cursor < header_fields_end) {
uint8_t field_code = *(uint8_t*)TRYPTR(wire_get_u8(c));
TRYPTR(wire_get_signature(c));
switch (field_code) {
case DBUS_HEADER_FIELD_PATH: /* through */
case DBUS_HEADER_FIELD_INTERFACE: /* through */
case DBUS_HEADER_FIELD_MEMBER: /* through */
case DBUS_HEADER_FIELD_DESTINATION: {
char *str = TRYPTR(wire_get_string(c));
WIRE_VERBOSE("field: %s\n", str);
msg->fields[field_code].t.str = str;
} break;
case DBUS_HEADER_FIELD_SIGNATURE: {
char *str = TRYPTR(wire_get_signature(c));
WIRE_VERBOSE("field: %s\n", str);
msg->fields[field_code].t.str = str;
} break;
case DBUS_HEADER_FIELD_REPLY_SERIAL: {
uint32_t u = *(uint32_t*)TRYPTR(wire_get_u32(c));
WIRE_VERBOSE("field: %d\n", u);
msg->fields[field_code].t.u32 = u;
} break;
default: {
printf("header: unknown field: %d\n", field_code);
return -1;
} break;
}
msg->fields[field_code].present = true;
// Structs are always aligned to `8`.
// SPEC: https://dbus.freedesktop.org/doc/dbus-specification.html#id-1.4.6
TRYPTR(wire_align(c, 8));
if (msg->header_fields_count++ > WIRE_MESSAGE_MAX_HEADER_FIELDS) {
return -1;
}
}
WIRE_VERBOSE("end parse message\n");
/* header ends at 8 byte boundry */
TRYPTR(wire_align(c, 8));
return 0;
}
int wire_compose_reply(wire_context_t *c, wire_message_t *msg, const char *signature, uint32_t **out_body_length)
{
TRYPTR(wire_set_u8(c, msg->endianness)); /* endianness */
TRYPTR(wire_set_u8(c, DBUS_MESSAGE_METHOD_RETURN)); /* type */
TRYPTR(wire_set_u8(c, 0)); /* flags */
TRYPTR(wire_set_u8(c, DBUS_PROTOCOL_VERSION)); /* protocol_version */
uint32_t *body_length = TRYPTR(wire_set_u32(c, 0)); /* body length */
TRYPTR(wire_set_u32(c, msg->serial+1)); /* serial */
uint32_t *header_fields_length = TRYPTR(wire_set_u32(c, 0)); /* header_fields_length */
uint32_t header_fields_start = c->byte_cursor;
/* header fields */
{
/* SIGNATURE */
{
/* byte (field code) */
TRYPTR(wire_set_u8(c, DBUS_HEADER_FIELD_SIGNATURE));
/* variant */
/* signature */
TRYPTR(wire_set_signature(c, "g"));
/* signature */
TRYPTR(wire_set_signature(c, signature));
// need to align to 8 byte boundry after each array element?
TRYPTR(wire_write_align(c, 8));
}
/* REPLY_SERIAL */
{
/* byte (field code) */
TRYPTR(wire_set_u8(c, DBUS_HEADER_FIELD_REPLY_SERIAL));
/* variant */
/* signature */
TRYPTR(wire_set_signature(c, "u"));
/* reply serial */
TRYPTR(wire_set_u32(c, msg->serial));
}
}
*header_fields_length = c->byte_cursor - header_fields_start;
// header ends on an 8 byte alignment
TRYPTR(wire_write_align(c, 8));
if (out_body_length) {
*out_body_length = body_length;
}
return 0;
}
int wire_compose_error(wire_context_t *c, wire_message_t *msg, const char *error_name)
{
TRYPTR(wire_set_u8(c, msg->endianness)); /* endianness */
TRYPTR(wire_set_u8(c, DBUS_MESSAGE_ERROR)); /* type */
TRYPTR(wire_set_u8(c, 0)); /* flags */
TRYPTR(wire_set_u8(c, DBUS_PROTOCOL_VERSION)); /* protocol_version */
TRYPTR(wire_set_u32(c, 0)); /* body length */
TRYPTR(wire_set_u32(c, msg->serial+1)); /* serial */
uint32_t *header_fields_length = TRYPTR(wire_set_u32(c, 0)); /* header_fields_length */
uint32_t header_fields_start = c->byte_cursor;
/* header fields */
{
/* ERROR_NAME */
{
/* byte (field code) */
TRYPTR(wire_set_u8(c, DBUS_HEADER_FIELD_ERROR_NAME));
/* variant */
/* signature */
TRYPTR(wire_set_signature(c, "s"));
/* string */
TRYPTR(wire_set_string(c, error_name));
// need to align to 8 byte boundry after each array element?
TRYPTR(wire_write_align(c, 8));
}
/* REPLY_SERIAL */
{
/* byte (field code) */
TRYPTR(wire_set_u8(c, DBUS_HEADER_FIELD_REPLY_SERIAL));
/* variant */
/* signature */
TRYPTR(wire_set_signature(c, "u"));
/* reply serial */
TRYPTR(wire_set_u32(c, msg->serial));
}
}
*header_fields_length = c->byte_cursor - header_fields_start;
// header ends on an 8 byte alignment
TRYPTR(wire_write_align(c, 8));
return 0;
}
int wire_compose_unicast_reply(wire_context_t *c, wire_context_t *msg_c, wire_message_t *msg, char *sender_unique_name)
{
TRYPTR(wire_set_u8(c, msg->endianness)); /* endianness */
TRYPTR(wire_set_u8(c, msg->type)); /* type */
TRYPTR(wire_set_u8(c, 0)); /* flags */
TRYPTR(wire_set_u8(c, DBUS_PROTOCOL_VERSION)); /* protocol_version */
TRYPTR(wire_set_u32(c, msg->body_length)); /* body length */
TRYPTR(wire_set_u32(c, msg->serial)); /* serial */
uint32_t *header_fields_length = TRYPTR(wire_set_u32(c, 0)); /* header_fields_length */
uint32_t header_fields_start = c->byte_cursor;
switch (msg->type) {
case DBUS_MESSAGE_METHOD_CALL: {
// these fields are required for DBUS_MESSAGE_METHOD_CALL, so we must (hackily) check for their presence
// interface is not strictly required by the spec, however we require it here anyways
wire_message_field_t *path_field = &msg->fields[DBUS_HEADER_FIELD_PATH];
wire_message_field_t *interface_field = &msg->fields[DBUS_HEADER_FIELD_INTERFACE];
wire_message_field_t *member_field = &msg->fields[DBUS_HEADER_FIELD_MEMBER];
wire_message_field_t *signature_field = &msg->fields[DBUS_HEADER_FIELD_SIGNATURE];
if (!path_field->present || !interface_field->present || !interface_field->present) {
return -1;
}
TRYPTR(wire_set_u8(c, DBUS_HEADER_FIELD_PATH));
TRYPTR(wire_set_signature(c, "o"));
TRYPTR(wire_set_string(c, path_field->t.str));
TRYPTR(wire_write_align(c, 8));
TRYPTR(wire_set_u8(c, DBUS_HEADER_FIELD_INTERFACE));
TRYPTR(wire_set_signature(c, "s"));
TRYPTR(wire_set_string(c, interface_field->t.str));
TRYPTR(wire_write_align(c, 8));
TRYPTR(wire_set_u8(c, DBUS_HEADER_FIELD_MEMBER));
TRYPTR(wire_set_signature(c, "s"));
TRYPTR(wire_set_string(c, member_field->t.str));
TRYPTR(wire_write_align(c, 8));
if (signature_field->present) {
TRYPTR(wire_set_u8(c, DBUS_HEADER_FIELD_SIGNATURE));
TRYPTR(wire_set_signature(c, "g"));
TRYPTR(wire_set_signature(c, signature_field->t.str));
TRYPTR(wire_write_align(c, 8));
}
TRYPTR(wire_set_u8(c, DBUS_HEADER_FIELD_SENDER));
TRYPTR(wire_set_signature(c, "s"));
TRYPTR(wire_set_string(c, sender_unique_name));
// last element, don't align
} break;
case DBUS_MESSAGE_METHOD_RETURN: {
wire_message_field_t *reply_serial_field = &msg->fields[DBUS_HEADER_FIELD_REPLY_SERIAL];
if (!reply_serial_field->present) {
return -1;
}
TRYPTR(wire_set_u8(c, DBUS_HEADER_FIELD_REPLY_SERIAL));
TRYPTR(wire_set_signature(c, "u"));
TRYPTR(wire_set_u32(c, reply_serial_field->t.u32));
TRYPTR(wire_write_align(c, 8));
wire_message_field_t *signature_field = &msg->fields[DBUS_HEADER_FIELD_SIGNATURE];
if (signature_field->present) {
TRYPTR(wire_set_u8(c, DBUS_HEADER_FIELD_SIGNATURE));
TRYPTR(wire_set_signature(c, "g"));
TRYPTR(wire_set_string(c, signature_field->t.str));
TRYPTR(wire_write_align(c, 8));
}
TRYPTR(wire_set_u8(c, DBUS_HEADER_FIELD_SENDER));
TRYPTR(wire_set_signature(c, "s"));
TRYPTR(wire_set_string(c, sender_unique_name));
// last element, don't align
} break;
default: {
return -1;
} break;
}
*header_fields_length = c->byte_cursor - header_fields_start;
// header ends on an 8 byte alignment
TRYPTR(wire_write_align(c, 8));
if (c->byte_cursor + msg->body_length > c->data_len) {
return -1;
}
memcpy(&c->data[c->byte_cursor], &msg_c->data[msg_c->byte_cursor], msg->body_length);
c->byte_cursor += msg->body_length;
return 0;
}
int wire_collect_strings(wire_context_t *c, wire_message_t *msg, wire_message_body_string_t *strings, int strings_count)
{
if (!msg->fields[DBUS_HEADER_FIELD_SIGNATURE].present) {
return -1;
}
char *sig = msg->fields[DBUS_HEADER_FIELD_SIGNATURE].t.str;
int stri = 0;
int sigi = 0;
uint32_t body_cursor = c->byte_cursor;
while (*sig) {
switch (*sig) {
case 'y': {
TRYPTR(wire_get_u8(c));
} break;
case 'b': {
TRYPTR(wire_get_u32(c));
} break;
case 'n': {
TRYPTR(wire_get_i16(c));
} break;
case 'q': {
TRYPTR(wire_get_u16(c));
} break;
case 'i': {
TRYPTR(wire_get_i32(c));
} break;
case 'u': {
TRYPTR(wire_get_u32(c));
} break;
case 'o': /* through */
case 's': {
char *s = TRYPTR(wire_get_string(c));
if (sigi >= strings_count) {
return -1;
}
strings[sigi].index = sigi;
strings[sigi].str = s;
} break;
case 'g': {
char *s = TRYPTR(wire_get_signature(c));
if (sigi >= strings_count) {
return -1;
}
strings[sigi].index = sigi;
strings[sigi].str = s;
} break;
case 'a': {
uint32_t array_size = *(uint32_t*)TRYPTR(wire_get_u32(c));
uint32_t new_cursor = c->byte_cursor + array_size;
if (new_cursor >= c->data_len) {
return -1;
}
c->byte_cursor = new_cursor;
} break;
default: {
return -1;
}
}
sigi++;
sig++;
}
c->byte_cursor = body_cursor;
return stri;
}