initial basic implementation (server + wire)

This commit is contained in:
hippoz 2022-12-21 00:38:10 +02:00
commit fbaffe515e
Signed by: hippoz
GPG key ID: 56C4E02A85F2FBED
10 changed files with 726 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
jitterbug

9
LICENSE Normal file
View file

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2022 hippoz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

5
Makefile Normal file
View file

@ -0,0 +1,5 @@
CC?=gcc
CFLAGS+=-Werror -Wall -Wextra -std=c99
jitterbug: wire.c server.c main.c
$(CC) $(CFLAGS) -o $@ $^

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# jitterbug
jitterbug is an experimental alternative D-Bus implementation focused on simplicity and control.

55
main.c Normal file
View file

@ -0,0 +1,55 @@
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include "server.h"
// https://en.wikipedia.org/wiki/Fowler-Noll-Vo_hash_function#FNV-1a_hash
uint64_t hashmap_hash(const char *bytes, size_t bytes_n, size_t map_len)
{
uint64_t hash = 0xcbf29ce484222325;
for (size_t i = 0; i < bytes_n; i++) {
hash *= 0x100000001b3;
hash ^= bytes[i];
}
return (hash % map_len);
}
const char *arg_shift(int *argc, char ***argv) {
if (*argc < 1)
return NULL;
const char *result = *argv[0];
*argc -= 1;
*argv += 1;
return result;
}
int main(int argc, char *argv[])
{
arg_shift(&argc, &argv);
const char *socket_path = arg_shift(&argc, &argv);
socket_path = socket_path == NULL ? "/tmp/jitterbug-unix0" : socket_path;
struct jb_server *srv = jb_server_create(socket_path);
if (srv == NULL) {
fprintf(stderr, "server_create failed\n");
return EXIT_FAILURE;
}
printf("Listening on %s\n", socket_path);
while (1) {
if (jb_server_turn(srv) < 0) {
fprintf(stderr, "server_turn failed\n");
jb_server_free(srv);
return EXIT_FAILURE;
}
}
jb_server_free(srv);
return EXIT_SUCCESS;
}

269
server.c Normal file
View file

@ -0,0 +1,269 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <string.h>
#include "try.h"
#include "server.h"
#include "wire.h"
int jb_server_client_add(struct jb_server *s, int fd)
{
for (int i = 0; i < JB_MAX_CLIENTS; i++) {
if (s->clients[i].fd < 0) {
s->clients[i].fd = fd;
s->clients[i].state = JB_CLIENT_STATE_WAIT_AUTH;
s->fds[i].fd = fd;
s->fds[i].events = POLLIN;
return i;
}
}
return -1;
}
void jb_server_client_remove(struct jb_server *s, int i)
{
if (s->clients[i].fd >= 0) {
close(s->clients[i].fd);
}
s->clients[i].fd = -1;
s->clients[i].state = JB_CLIENT_STATE_NONE;
s->fds[i].fd = -1;
s->fds[i].events = 0;
s->fds[i].revents = 0;
}
void jb_server_client_error(struct jb_server *s, int i, const char *msg)
{
printf("jb_server_client_error: %s\n", msg);
send(s->clients[i].fd, msg, strlen(msg), 0);
jb_server_client_remove(s, i);
}
ssize_t jb_server_client_recv(struct jb_server *s, int i, void *buf, size_t n)
{
ssize_t status = recv(s->fds[i].fd, buf, n, 0);
if (status <= 0) {
jb_server_client_remove(s, i);
}
return status;
}
int jb_server_client_process_message(struct jb_server *s, int i, uint8_t *data, size_t data_len)
{
wire_context_t ctx = {
.byte_cursor = 0,
.data = data,
.data_len = data_len,
};
wire_message_t msg = {0};
TRY_NONNEGATIVE(int, wire_parse_message(&ctx, &msg), -1);
printf("length: %d, serial: %d, header fields length in bytes: %d\n", msg.body_length, msg.serial, msg.header_fields_length);
switch (msg.type) {
case DBUS_MESSAGE_METHOD_CALL: {
bool for_dbus = false;
char *member;
// TODO: maybe the fields in the msg.fields array should be indexed based on their type?
for (int i = 0; i < msg.header_fields_count; i++) {
if (msg.fields[i].type == DBUS_HEADER_FIELD_DESTINATION) {
if (strcmp(msg.fields[i].t.str, "org.freedesktop.DBus") == 0) {
for_dbus = true;
}
} else if (msg.fields[i].type == DBUS_HEADER_FIELD_MEMBER) {
member = msg.fields[i].t.str;
}
}
if (for_dbus) {
if (strcmp(member, "Hello") == 0) {
printf("jb_server_client_process_message: message was for dbus, got hello\n");
uint8_t reply_data[512];
memset(reply_data, 0, 512);
wire_context_t reply_ctx = {
.byte_cursor = 0,
.data = reply_data,
.data_len = 511,
};
uint32_t *body_length;
TRY_NONNEGATIVE(int, wire_compose_reply(&reply_ctx, &msg, "s", &body_length), -1);
uint32_t body_start = reply_ctx.byte_cursor;
TRY_NONNULL(char*, wire_set_string(&reply_ctx, ":1.0"), -1);
*body_length = reply_ctx.byte_cursor - body_start;
if (send(s->fds[i].fd, reply_data, reply_ctx.byte_cursor, 0) != reply_ctx.byte_cursor) {
return -1;
}
printf("jb_server_client_process_message: processed hello\n");
} else {
printf("jb_server_client_process_message: message was for dbus, however got unimplemented member '%s'\n", member);
}
} else {
printf("jb_server_client_process_message: skipping message not for dbus for now\n");
}
} break;
default: {
printf("jb_server_client_process_message: unimplemented message type: %d\n", msg.type);
} break;
}
return 0;
}
void jb_server_free(struct jb_server *s)
{
if (!s) return;
if (s->sock_fd) close(s->sock_fd);
free(s);
}
struct jb_server *jb_server_create(const char *socket_path)
{
struct jb_server *s = malloc(sizeof(struct jb_server));
if (s == NULL) {
return NULL;
}
s->sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (s->sock_fd == -1) {
free(s);
return NULL;
}
struct sockaddr_un name;
memset(&name, 0, sizeof(name));
name.sun_family = AF_UNIX;
strncpy(name.sun_path, socket_path, sizeof(name.sun_path) - 1);
unlink(socket_path);
if (bind(s->sock_fd, (const struct sockaddr *)&name, sizeof(name)) == -1) {
close(s->sock_fd);
free(s);
return NULL;
}
if (listen(s->sock_fd, JB_BACKLOG) == -1) {
close(s->sock_fd);
free(s);
return NULL;
}
for (int i = 0; i < JB_MAX_CLIENTS; i++) {
s->clients[i].fd = -1;
s->clients[i].state = JB_CLIENT_STATE_NONE;
s->fds[i].fd = -1;
s->fds[i].events = 0;
s->fds[i].revents = 0;
}
s->fds[JB_MAX_CLIENTS].fd = s->sock_fd;
s->fds[JB_MAX_CLIENTS].events = POLLIN;
s->fd_num = JB_MAX_CLIENTS + 1;
return s;
}
#define _client_die(m) ({jb_server_client_error(s,i,m);continue;})
int jb_server_turn(struct jb_server *s)
{
static const char agree_unix_fd[] = "AGREE_UNIX_FD\r\n";
static const char auth_ok[] = "OK 1234deadbeef\r\n";
static const int data_buffer_len = 256;
TRY_NONNEGATIVE(int, poll(s->fds, s->fd_num, -1), -1);
for (int i = 0; i < s->fd_num; i++) {
int fd = s->fds[i].fd;
if (s->fds[i].revents & POLLIN) {
// file descriptor ready for reading
if (fd == s->sock_fd) {
// new connection
int fd = TRY_NONNEGATIVE(int, accept(s->sock_fd, NULL, NULL), -1);
if (jb_server_client_add(s, fd) == -1) {
close(fd);
}
continue;
}
char data[data_buffer_len];
memset(data, 0, sizeof(data));
ssize_t bytes = recv(fd, data, sizeof(data) - 1, 0);
if (bytes < 0) {
// error during recv(), disconnect the client
// TODO: should we actually do this?
close(fd);
jb_server_client_remove(s, i);
return -1;
}
if (bytes == 0) {
// client disconnected
jb_server_client_remove(s, i);
continue;
}
struct jb_client *c = &s->clients[i];
switch (c->state) {
case JB_CLIENT_STATE_WAIT_AUTH: {
// The D-Bus authentication protocol is a simple plain text protocol.
// Before the flow of messages can begin, the two applications must authenticate.
// SPEC: https://dbus.freedesktop.org/doc/dbus-specification.html#auth-protocol
// Immediately after connecting, clients must send a null byte
// SPEC: https://dbus.freedesktop.org/doc/dbus-specification.html#auth-nul-byte
if (*data != '\0' || *(data + 1) == '\0') {
_client_die("expected initial auth message to begin with a nul byte");
}
char *auth_string = data + 1;
if (strcmp(auth_string, "AUTH EXTERNAL 31303030\r\n") != 0)
_client_die("bad auth");
send(fd, auth_ok, sizeof(auth_ok) - 1, 0);
c->state = JB_CLIENT_STATE_WAIT_BEGIN;
} break;
case JB_CLIENT_STATE_WAIT_BEGIN: {
// Right now, we're expecting the client to either immediately begin the connection,
// or to negotiate UNIX file descriptor passing.
if (strncmp(data, "BEGIN\r\n", 7) == 0) {
c->state = JB_CLIENT_STATE_READY;
// At this point, a D-Bus connection has been established.
// The first octet after the \r\n of the BEGIN command is the first octet of the D-Bus communication.
// SPEC: https://dbus.freedesktop.org/doc/dbus-specification.html#auth-command-begin
char *first_message_begin = data + 7; /* 7 = length of "BEGIN\r\n" */
if (*first_message_begin == 'l' || *first_message_begin == 'B') {
// This looks like a D-Bus message. Let's process it!
if (jb_server_client_process_message(s, i, (uint8_t *)first_message_begin, data_buffer_len - 7) < 0) {
_client_die("failed to process message");
}
}
} else if (strcmp(data, "NEGOTIATE_UNIX_FD\r\n") == 0) {
// SPEC: https://dbus.freedesktop.org/doc/dbus-specification.html#auth-command-negotiate-unix-fd
send(fd, agree_unix_fd, sizeof(agree_unix_fd) - 1, 0);
} else {
_client_die("bad auth response (expected BEGIN or NEGOTIATE_UNIX_FD)");
}
} break;
case JB_CLIENT_STATE_READY: {
jb_server_client_process_message(s, i, (uint8_t*)data, data_buffer_len);
} break;
case JB_CLIENT_STATE_NONE: {} /* through */
default: {
_client_die("bad state");
} break;
}
}
}
return 0;
}

36
server.h Normal file
View file

@ -0,0 +1,36 @@
#ifndef _JITTERBUG__SERVER_H
#define _JITTERBUG__SERVER_H
#include <poll.h>
#define JB_MAX_CLIENTS 128
#define JB_BACKLOG 10
enum {
JB_CLIENT_STATE_NONE,
JB_CLIENT_STATE_WAIT_AUTH,
JB_CLIENT_STATE_WAIT_BEGIN,
JB_CLIENT_STATE_READY
};
struct jb_client {
int fd;
int state;
};
struct jb_server {
int sock_fd;
int fd_num;
struct jb_client clients[JB_MAX_CLIENTS];
struct pollfd fds[JB_MAX_CLIENTS + 1];
};
int jb_server_client_add(struct jb_server *s, int fd);
void jb_server_client_remove(struct jb_server *s, int i);
void jb_server_free(struct jb_server *s);
struct jb_server *jb_server_create(const char *socket_path);
int jb_server_turn(struct jb_server *s);
#endif // _JITTERBUG__SERVER_H

2
try.h Normal file
View file

@ -0,0 +1,2 @@
#define TRY_NONNULL(M_t, M_expr, M_sentinel) ({ M_t _result = (M_expr); if (!_result) { return (M_sentinel); } _result; })
#define TRY_NONNEGATIVE(M_t, M_expr, M_sentinel) ({ M_t _result = (M_expr); if (_result < 0) { return (M_sentinel); } _result; })

230
wire.c Normal file
View file

@ -0,0 +1,230 @@
#include <string.h>
#include <stdio.h>
#include "try.h"
#include "wire.h"
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(wire_context_t *c, uint32_t *out_len)
{
uint32_t len = *TRY_NONNULL(uint32_t*, wire_get_u32(c), NULL);
if (out_len) {
*out_len = 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;
}
char *str = (char *)&c->data[c->byte_cursor];
c->byte_cursor = new_cursor;
return str;
}
char *wire_set_string(wire_context_t *c, const char *str)
{
uint32_t len = strlen(str);
TRY_NONNULL(uint32_t*, wire_set_u32(c, len), NULL);
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;
}
char *wire_get_signature(wire_context_t *c, uint8_t *out_len)
{
uint8_t len = *TRY_NONNULL(uint8_t*, wire_get_u8(c), NULL);
if (out_len) {
*out_len = 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;
}
char *str = (char *)&c->data[c->byte_cursor];
c->byte_cursor = new_cursor;
return str;
}
char *wire_set_signature(wire_context_t *c, const char *str)
{
uint8_t len = strlen(str);
TRY_NONNULL(uint8_t*, wire_set_u8(c, len), NULL);
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 = *TRY_NONNULL(uint8_t*, wire_get_u8(c), -1);
msg->type = *TRY_NONNULL(uint8_t*, wire_get_u8(c), -1);
msg->flags = *TRY_NONNULL(uint8_t*, wire_get_u8(c), -1);
msg->protocol_version = *TRY_NONNULL(uint8_t*, wire_get_u8(c), -1);
msg->body_length = *TRY_NONNULL(uint32_t*, wire_get_u32(c), -1);
msg->serial = *TRY_NONNULL(uint32_t*, wire_get_u32(c), -1);
msg->header_fields_length = *TRY_NONNULL(uint32_t*, wire_get_u32(c), -1);
size_t header_fields_end = c->byte_cursor + msg->header_fields_length;
while (c->byte_cursor < header_fields_end) {
uint8_t field_code = *TRY_NONNULL(uint8_t*, wire_get_u8(c), -1);
TRY_NONNULL(char*, wire_get_signature(c, NULL), -1);
msg->fields[msg->header_fields_count].type = field_code;
switch (field_code) {
case DBUS_HEADER_FIELD_PATH: {
char *str = TRY_NONNULL(char*, wire_get_string(c, NULL), -1);
msg->fields[msg->header_fields_count].t.str = str;
printf("DBUS_HEADER_FIELD_PATH: %s\n", str);
} break;
case DBUS_HEADER_FIELD_INTERFACE: {
char *str = TRY_NONNULL(char*, wire_get_string(c, NULL), -1);
msg->fields[msg->header_fields_count].t.str = str;
printf("DBUS_HEADER_FIELD_INTERFACE: %s\n", str);
} break;
case DBUS_HEADER_FIELD_MEMBER: {
char *str = TRY_NONNULL(char*, wire_get_string(c, NULL), -1);
msg->fields[msg->header_fields_count].t.str = str;
printf("DBUS_HEADER_FIELD_MEMBER: %s\n", str);
} break;
case DBUS_HEADER_FIELD_DESTINATION: {
char *str = TRY_NONNULL(char*, wire_get_string(c, NULL), -1);
msg->fields[msg->header_fields_count].t.str = str;
printf("DBUS_HEADER_FIELD_DESTINATION: %s\n", str);
} break;
default: {
printf("header: unknown field: %d\n", field_code);
return -1;
} break;
}
// Structs are always aligned to `8`.
// SPEC: https://dbus.freedesktop.org/doc/dbus-specification.html#id-1.4.6
TRY_NONNULL(bool, wire_align(c, 8), -1);
if (msg->header_fields_count++ > WIRE_MESSAGE_MAX_HEADER_FIELDS) {
return -1;
}
}
return 0;
}
int wire_compose_reply(wire_context_t *c, wire_message_t *msg, const char *signature, uint32_t **out_body_length)
{
TRY_NONNULL(uint8_t*, wire_set_u8(c, msg->endianness), -1); /* endianness */
TRY_NONNULL(uint8_t*, wire_set_u8(c, DBUS_MESSAGE_METHOD_RETURN), -1); /* type */
TRY_NONNULL(uint8_t*, wire_set_u8(c, 0), -1); /* flags */
TRY_NONNULL(uint8_t*, wire_set_u8(c, DBUS_PROTOCOL_VERSION), -1); /* protocol_version */
uint32_t *body_length = TRY_NONNULL(uint32_t*, wire_set_u32(c, 0), -1); /* body length */
TRY_NONNULL(uint32_t*, wire_set_u32(c, msg->serial+1), -1); /* serial */
uint32_t *header_fields_length = TRY_NONNULL(uint32_t*, wire_set_u32(c, 0), -1); /* header_fields_length */
uint32_t header_fields_start = c->byte_cursor;
/* header fields */
{
/* SIGNATURE */
{
/* byte (field code) */
TRY_NONNULL(uint8_t*, wire_set_u8(c, DBUS_HEADER_FIELD_SIGNATURE), -1);
/* variant */
/* signature */
TRY_NONNULL(char*, wire_set_signature(c, "g"), -1);
/* signature */
TRY_NONNULL(char*, wire_set_signature(c, signature), -1);
// need to align to 8 byte boundry after each array element?
TRY_NONNULL(bool, wire_write_align(c, 8), -1);
}
/* REPLY_SERIAL */
{
/* byte (field code) */
TRY_NONNULL(uint8_t*, wire_set_u8(c, DBUS_HEADER_FIELD_REPLY_SERIAL), -1);
/* variant */
/* signature */
TRY_NONNULL(char*, wire_set_signature(c, "u"), -1);
/* reply serial */
TRY_NONNULL(uint32_t*, wire_set_u32(c, msg->serial), -1);
}
}
*header_fields_length = c->byte_cursor - header_fields_start;
// header ends on an 8 byte alignment
TRY_NONNULL(bool, wire_write_align(c, 8), -1);
if (out_body_length) {
*out_body_length = body_length;
}
return 0;
}

116
wire.h Normal file
View file

@ -0,0 +1,116 @@
#ifndef _JITTERBUG__WIRE_H
#define _JITTERBUG__WIRE_H
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#define DBUS_PROTOCOL_VERSION 1
enum {
DBUS_HEADER_FIELD_INVALID,
DBUS_HEADER_FIELD_PATH,
DBUS_HEADER_FIELD_INTERFACE,
DBUS_HEADER_FIELD_MEMBER,
DBUS_HEADER_FIELD_ERROR_NAME,
DBUS_HEADER_FIELD_REPLY_SERIAL,
DBUS_HEADER_FIELD_DESTINATION,
DBUS_HEADER_FIELD_SENDER,
DBUS_HEADER_FIELD_SIGNATURE,
DBUS_HEADER_FIELD_UNIX_FDS,
};
enum {
DBUS_MESSAGE_INVALID,
DBUS_MESSAGE_METHOD_CALL,
DBUS_MESSAGE_METHOD_RETURN,
DBUS_MESSAGE_ERROR,
DBUS_MESSAGE_SIGNAL,
};
typedef struct wire_context {
uint32_t byte_cursor;
uint8_t *data;
size_t data_len;
} wire_context_t;
// SPEC: https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-messages
typedef struct wire_message_field {
uint8_t type;
union {
char *str;
} t;
} wire_message_field_t;
#define WIRE_MESSAGE_MAX_HEADER_FIELDS 9
typedef struct wire_message {
uint8_t endianness, type, flags, protocol_version;
uint32_t body_length, serial, header_fields_length;
uint8_t header_fields_count;
wire_message_field_t fields[WIRE_MESSAGE_MAX_HEADER_FIELDS]; /* cannot have more than 9 header fields */
} wire_message_t;
#define _WIRE_DECL_BYTE_TYPE(M_mnemonic, M_type) \
M_type *wire_get_##M_mnemonic(wire_context_t *c); \
M_type *wire_set_##M_mnemonic(wire_context_t *c, M_type val); \
#define _WIRE_DECL_TYPE(M_mnemonic, M_type, M_alignment) \
M_type *wire_get_##M_mnemonic(wire_context_t *c); \
M_type *wire_set_##M_mnemonic(wire_context_t *c, M_type val); \
#define _WIRE_DEF_BYTE_TYPE(M_mnemonic, M_type) \
M_type *wire_get_##M_mnemonic(wire_context_t *c) { \
uint32_t new_cursor = c->byte_cursor + 1; \
if (new_cursor >= c->data_len) return NULL; \
M_type *value = (M_type*)&c->data[c->byte_cursor]; \
c->byte_cursor = new_cursor; \
return value; \
} \
M_type *wire_set_##M_mnemonic(wire_context_t *c, M_type val) { \
uint32_t new_cursor = c->byte_cursor + 1; \
if (new_cursor >= c->data_len) return NULL; \
M_type *dest = (M_type*)&c->data[c->byte_cursor]; \
*dest = val; \
c->byte_cursor = new_cursor; \
return dest; \
} \
#define _WIRE_DEF_TYPE(M_mnemonic, M_type, M_alignment) \
M_type *wire_get_##M_mnemonic(wire_context_t *c) { \
if (!wire_align(c, M_alignment)) return NULL; \
uint32_t new_cursor = c->byte_cursor + sizeof(M_type); \
if (new_cursor >= c->data_len) return NULL; \
M_type *value = (M_type*)&c->data[c->byte_cursor]; \
c->byte_cursor = new_cursor; \
return value; \
} \
M_type *wire_set_##M_mnemonic(wire_context_t *c, M_type val) { \
if (!wire_write_align(c, M_alignment)) return NULL; \
uint32_t new_cursor = c->byte_cursor + sizeof(M_type); \
if (new_cursor >= c->data_len) return NULL; \
M_type *dest = (M_type*)&c->data[c->byte_cursor]; \
*dest = val; \
c->byte_cursor = new_cursor; \
return dest; \
} \
bool wire_align(wire_context_t *c, uint32_t alignment);
bool wire_write_align(wire_context_t *c, uint32_t alignment);
// SPEC: https://dbus.freedesktop.org/doc/dbus-specification.html#id-1.4.6
_WIRE_DECL_BYTE_TYPE(i8, int8_t);
_WIRE_DECL_BYTE_TYPE(u8, uint8_t);
_WIRE_DECL_TYPE(i16, int16_t, 2);
_WIRE_DECL_TYPE(u16, uint16_t, 2);
_WIRE_DECL_TYPE(i32, int32_t, 4);
_WIRE_DECL_TYPE(u32, uint32_t, 4);
_WIRE_DECL_TYPE(boolean, uint32_t, 4);
char *wire_get_string(wire_context_t *c, uint32_t *out_len);
char *wire_set_string(wire_context_t *c, const char *str);
char *wire_get_signature(wire_context_t *c, uint8_t *out_len);
char *wire_set_signature(wire_context_t *c, const char *str);
int wire_parse_message(wire_context_t *c, wire_message_t *msg);
int wire_compose_reply(wire_context_t *c, wire_message_t *msg, const char *signature, uint32_t **out_body_length);
#endif // _JITTERBUG__WIRE_H