libei: move the protocol parsing into a separate file

The goal here is to separate any state checking and other processing from the
actual bits writing onto the wire so we can test those separately.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
Peter Hutterer 2020-08-18 16:36:36 +10:00
parent 2b9d5d960a
commit 6de95310e1
4 changed files with 451 additions and 290 deletions

View file

@ -39,6 +39,8 @@ src_libei = [
'src/libei-device.c',
'src/libei-socket.c',
'src/libei-fd.c',
'src/libei-proto.h',
'src/libei-proto.c',
proto_headers,
]

349
src/libei-proto.c Normal file
View file

@ -0,0 +1,349 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* 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 (including the next
* paragraph) 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.
*/
#include "config.h"
#include <stdbool.h>
#include "util-object.h"
#include "util-io.h"
#include "util-logger.h"
#include "proto/ei.pb-c.h"
#include "libei-proto.h"
void
message_free(struct message *msg)
{
switch (msg->type) {
default:
break;
}
free(msg);
}
static void frame_cleanup(Frame **f) {
if (*f)
frame__free_unpacked(*f, NULL);
}
static void servermessage_cleanup(ServerMessage **m) {
if (*m)
server_message__free_unpacked(*m, NULL);
}
#define _cleanup_frame_ _cleanup_(frame_cleanup)
#define _cleanup_servermessage_ _cleanup_(servermessage_cleanup)
static const unsigned char *
next_message(const char *data, size_t *msglen, size_t *consumed)
{
/* Every message is prefixed by a fixed-length Frame message which
* contains the length of the next message. Frames are always the
* same length, so we only need to calculate the size once.
*/
static size_t framelen = 0;
if (framelen == 0) {
Frame f = FRAME__INIT;
f.length = 0xffff;
framelen = frame__get_packed_size(&f);
assert(framelen >= 5);
}
_cleanup_frame_ Frame *frame = frame__unpack(NULL, framelen, (const unsigned char *)data);
if (!frame)
return NULL;
*msglen = frame->length;
*consumed = framelen;
return (const unsigned char*)data + framelen;
}
struct message *
ei_proto_parse_message(const char *data, size_t len, size_t *consumed)
{
size_t headerbytes = 0;
size_t msglen = 0;
const unsigned char *msgdata = next_message(data, &msglen, &headerbytes);
_cleanup_servermessage_ ServerMessage *proto = server_message__unpack(NULL, msglen, msgdata);
if (!proto)
return NULL;
*consumed = headerbytes + msglen;
_cleanup_message_ struct message *msg = xalloc(sizeof(*msg));
bool success = true;
switch (proto->msg_case) {
case SERVER_MESSAGE__MSG_CONNECTED:
*msg = (struct message) {
.type = MESSAGE_CONNECTED,
};
break;
case SERVER_MESSAGE__MSG_DISCONNECTED:
*msg = (struct message) {
.type = MESSAGE_DISCONNECTED,
};
break;
case SERVER_MESSAGE__MSG_DEVICE_ADDED:
{
DeviceAdded *a = proto->device_added;
*msg = (struct message) {
.type = MESSAGE_DEVICE_ADDED,
.added.deviceid = a->deviceid,
.added.capabilities = a->capabilities,
};
}
break;
case SERVER_MESSAGE__MSG_DEVICE_REMOVED:
{
DeviceRemoved *r = proto->device_removed;
*msg = (struct message) {
.type = MESSAGE_DEVICE_REMOVED,
.removed.deviceid = r->deviceid,
};
}
break;
default:
success = false;
break;
}
return success ? steal(&msg) : NULL;
}
static inline void
log_wire_message(struct ei *ei, const ClientMessage *msg)
{
const char *message = NULL;
#define MSG_STRING_CASE(_name) \
case CLIENT_MESSAGE__MSG_##_name: message = #_name; break;
switch (msg->msg_case) {
case CLIENT_MESSAGE__MSG__NOT_SET:
abort();
MSG_STRING_CASE(CONNECT);
MSG_STRING_CASE(DISCONNECT);
MSG_STRING_CASE(ADD_DEVICE);
MSG_STRING_CASE(REMOVE_DEVICE);
MSG_STRING_CASE(REL);
MSG_STRING_CASE(BUTTON);
MSG_STRING_CASE(KEY);
MSG_STRING_CASE(CONFIGURE_NAME);
MSG_STRING_CASE(CONFIGURE_CAPS);
default:
assert(!"Unimplemented message type");
break;
}
log_debug(ei, "sending wire message %s\n", message);
#undef MSG_STRING_CASE
}
static int
ei_proto_send_msg(struct ei *ei, const ClientMessage *msg)
{
log_wire_message(ei, msg);
size_t msglen = client_message__get_packed_size(msg);
Frame frame = FRAME__INIT;
frame.length = msglen;
size_t framelen = frame__get_packed_size(&frame);
uint8_t buf[framelen + msglen];
frame__pack(&frame, buf);
client_message__pack(msg, buf + framelen);
return min(0, xsend(source_get_fd(ei->source), buf, sizeof(buf)));
}
int
ei_proto_send_connect(struct ei *ei)
{
ClientMessage msg = CLIENT_MESSAGE__INIT;
Connect connect = CONNECT__INIT;
connect.name = ei->name;
msg.connect = &connect;
msg.msg_case = CLIENT_MESSAGE__MSG_CONNECT;
return ei_proto_send_msg(ei, &msg);
}
int
ei_proto_send_disconnect(struct ei *ei)
{
ClientMessage msg = CLIENT_MESSAGE__INIT;
Disconnect disconnect = DISCONNECT__INIT;
msg.disconnect = &disconnect;
msg.msg_case = CLIENT_MESSAGE__MSG_DISCONNECT;
return ei_proto_send_msg(ei, &msg);
}
int
ei_proto_send_add(struct ei *ei, struct ei_device *device)
{
ClientMessage msg = CLIENT_MESSAGE__INIT;
AddDevice add = ADD_DEVICE__INIT;
add.deviceid = device->id;
add.capabilities = device->capabilities;
add.pointer_width = device->abs.dim.width;
add.pointer_height = device->abs.dim.height;
add.touch_width = device->touch.dim.width;
add.touch_height = device->touch.dim.height;
msg.add_device = &add;
msg.msg_case = CLIENT_MESSAGE__MSG_ADD_DEVICE;
return ei_proto_send_msg(ei, &msg);
}
int
ei_proto_send_remove(struct ei *ei, struct ei_device *device)
{
ClientMessage msg = CLIENT_MESSAGE__INIT;
RemoveDevice remove = REMOVE_DEVICE__INIT;
remove.deviceid = device->id;
msg.remove_device = &remove;
msg.msg_case = CLIENT_MESSAGE__MSG_REMOVE_DEVICE;
return ei_proto_send_msg(ei, &msg);
}
int
ei_proto_send_rel(struct ei *ei, struct ei_device *device,
double x, double y)
{
ClientMessage msg = CLIENT_MESSAGE__INIT;
PointerRelative rel = POINTER_RELATIVE__INIT;
rel.deviceid = device->id;
rel.x = x;
rel.y = y;
msg.rel = &rel;
msg.msg_case = CLIENT_MESSAGE__MSG_REL;
return ei_proto_send_msg(ei, &msg);
}
int
ei_proto_send_button(struct ei *ei, struct ei_device *device,
uint32_t b, bool is_press)
{
ClientMessage msg = CLIENT_MESSAGE__INIT;
PointerButton button = POINTER_BUTTON__INIT;
button.deviceid = device->id;
button.button = b;
button.state = is_press;
msg.button = &button;
msg.msg_case = CLIENT_MESSAGE__MSG_BUTTON;
return ei_proto_send_msg(ei, &msg);
}
int
ei_proto_send_key(struct ei *ei, struct ei_device *device,
uint32_t k, bool is_press)
{
ClientMessage msg = CLIENT_MESSAGE__INIT;
KeyboardKey key = KEYBOARD_KEY__INIT;
key.deviceid = device->id;
key.key = k;
key.state = is_press;
msg.key = &key;
msg.msg_case = CLIENT_MESSAGE__MSG_KEY;
return ei_proto_send_msg(ei, &msg);
}
#ifdef _enable_tests_
#include <munit.h>
static MunitResult
test_proto_next_message(const MunitParameter params[], void *user_data)
{
char data[64];
/* Invalid frame, rest can be random */
data[0] = 0xaa;
size_t msglen = 0xab;
size_t consumed = 0xbc;
const unsigned char *rval = next_message(data, &msglen, &consumed);
munit_assert_ptr_null(rval);
munit_assert_int(msglen, ==, 0xab);
munit_assert_int(consumed, ==, 0xbc);
/* Now try a valid one */
Frame f = FRAME__INIT;
f.length = 0xcd;
size_t framelen = frame__get_packed_size(&f);
unsigned char buf[framelen * 4];
for (int i = 0; i < 4; i++)
frame__pack(&f, buf + framelen * i);
const char *ptr = (char*)buf;
for (int i = 0; i < 4; i++) {
const unsigned char *next_frame = next_message(ptr, &msglen, &consumed);
munit_assert_ptr_equal(next_frame, buf + (i + 1) * framelen);
munit_assert_int(consumed, ==, framelen);
munit_assert_int(msglen, ==, 0xcd);
ptr += consumed;
}
return MUNIT_OK;
}
#define TEST(_func) \
{ .name = #_func, .test = _func }
static MunitTest ei_tests[] = {
TEST(test_proto_next_message),
{ NULL },
};
static const MunitSuite libei_suite
__attribute__((used))
__attribute__((section("test_section"))) = {
"",
ei_tests,
NULL,
1,
MUNIT_SUITE_OPTION_NONE,
};
#endif

90
src/libei-proto.h Normal file
View file

@ -0,0 +1,90 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* 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 (including the next
* paragraph) 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.
*/
#pragma once
#include "config.h"
#include "util-macros.h"
#include "util-mem.h"
#include "libei-private.h"
/* The message type for the wire format */
enum message_type {
MESSAGE_CONNECTED = 1,
MESSAGE_DISCONNECTED,
MESSAGE_DEVICE_ADDED,
MESSAGE_DEVICE_REMOVED,
};
struct message {
enum message_type type;
union {
struct message_connected {
uint8_t pad; /* no data */
} connected;
struct message_disconnected {
uint8_t pad; /* no data */
} disconnected;
struct message_device_added {
uint32_t deviceid;
uint32_t capabilities;
} added;
struct message_device_removed {
uint32_t deviceid;
} removed;
};
};
void message_free(struct message *msg);
DEFINE_TRIVIAL_CLEANUP_FUNC(struct message*, message_free);
#define _cleanup_message_ _cleanup_(message_freep)
struct message *
ei_proto_parse_message(const char *data, size_t len, size_t *consumed);
int
ei_proto_send_connect(struct ei *ei);
int
ei_proto_send_disconnect(struct ei *ei);
int
ei_proto_send_add(struct ei *ei, struct ei_device *device);
int
ei_proto_send_remove(struct ei *ei, struct ei_device *device);
int
ei_proto_send_rel(struct ei *ei, struct ei_device *device,
double x, double y);
int
ei_proto_send_button(struct ei *ei, struct ei_device *device,
uint32_t b, bool is_press);
int
ei_proto_send_key(struct ei *ei, struct ei_device *device,
uint32_t k, bool is_press);

View file

@ -37,48 +37,7 @@
#include "libei.h"
#include "libei-private.h"
#include "proto/ei.pb-c.h"
/* The message type for the wire format */
enum message_type {
MESSAGE_CONNECTED = 1,
MESSAGE_DISCONNECTED,
MESSAGE_DEVICE_ADDED,
MESSAGE_DEVICE_REMOVED,
};
struct message {
enum message_type type;
union {
struct message_connected {
uint8_t pad; /* no data */
} connected;
struct message_disconnected {
uint8_t pad; /* no data */
} disconnected;
struct MESSAGE_DEVICE_ADDED {
uint32_t deviceid;
uint32_t capabilities;
} added;
struct MESSAGE_DEVICE_REMOVED {
uint32_t deviceid;
} removed;
};
};
static void
message_free(struct message *msg)
{
switch (msg->type) {
default:
break;
}
free(msg);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(struct message*, message_free);
#define _cleanup_message_ _cleanup_(message_freep)
#include "libei-proto.h"
static void
ei_event_destroy(struct ei_event *event)
@ -219,52 +178,6 @@ ei_queue_removed_event(struct ei_device *device)
ei_queue_event(ei, e);
}
static inline void
log_wire_message(struct ei *ei, const ClientMessage *msg)
{
const char *message = NULL;
#define MSG_STRING_CASE(_name) \
case CLIENT_MESSAGE__MSG_##_name: message = #_name; break;
switch (msg->msg_case) {
case CLIENT_MESSAGE__MSG__NOT_SET:
abort();
MSG_STRING_CASE(CONNECT);
MSG_STRING_CASE(DISCONNECT);
MSG_STRING_CASE(ADD_DEVICE);
MSG_STRING_CASE(REMOVE_DEVICE);
MSG_STRING_CASE(REL);
MSG_STRING_CASE(BUTTON);
MSG_STRING_CASE(KEY);
MSG_STRING_CASE(CONFIGURE_NAME);
MSG_STRING_CASE(CONFIGURE_CAPS);
default:
assert(!"Unimplemented message type");
break;
}
log_debug(ei, "sending wire message %s\n", message);
#undef MSG_STRING_CASE
}
static int
connection_send_msg(struct ei *ei, const ClientMessage *msg)
{
log_wire_message(ei, msg);
size_t msglen = client_message__get_packed_size(msg);
Frame frame = FRAME__INIT;
frame.length = msglen;
size_t framelen = frame__get_packed_size(&frame);
uint8_t buf[framelen + msglen];
frame__pack(&frame, buf);
client_message__pack(msg, buf + framelen);
return min(0, xsend(source_get_fd(ei->source), buf, sizeof(buf)));
}
static int
connection_send_connect(struct ei *ei)
{
@ -272,14 +185,7 @@ connection_send_connect(struct ei *ei)
ei->state == EI_STATE_DISCONNECTED)
return 0;
ClientMessage msg = CLIENT_MESSAGE__INIT;
Connect connect = CONNECT__INIT;
connect.name = ei->name;
msg.connect = &connect;
msg.msg_case = CLIENT_MESSAGE__MSG_CONNECT;
return connection_send_msg(ei, &msg);
return ei_proto_send_connect(ei);
}
static int
@ -289,13 +195,7 @@ connection_send_disconnect(struct ei *ei)
ei->state == EI_STATE_DISCONNECTED)
return 0;
ClientMessage msg = CLIENT_MESSAGE__INIT;
Disconnect disconnect = DISCONNECT__INIT;
msg.disconnect = &disconnect;
msg.msg_case = CLIENT_MESSAGE__MSG_DISCONNECT;
return connection_send_msg(ei, &msg);
return ei_proto_send_disconnect(ei);
}
static int
@ -305,20 +205,7 @@ connection_send_add(struct ei *ei, struct ei_device *device)
ei->state == EI_STATE_DISCONNECTED)
return 0;
ClientMessage msg = CLIENT_MESSAGE__INIT;
AddDevice add = ADD_DEVICE__INIT;
add.deviceid = device->id;
add.capabilities = device->capabilities;
add.pointer_width = device->abs.dim.width;
add.pointer_height = device->abs.dim.height;
add.touch_width = device->touch.dim.width;
add.touch_height = device->touch.dim.height;
msg.add_device = &add;
msg.msg_case = CLIENT_MESSAGE__MSG_ADD_DEVICE;
return connection_send_msg(ei, &msg);
return ei_proto_send_add(ei, device);
}
static int
@ -328,15 +215,7 @@ connection_send_remove(struct ei *ei, struct ei_device *device)
ei->state == EI_STATE_DISCONNECTED)
return 0;
ClientMessage msg = CLIENT_MESSAGE__INIT;
RemoveDevice remove = REMOVE_DEVICE__INIT;
remove.deviceid = device->id;
msg.remove_device = &remove;
msg.msg_case = CLIENT_MESSAGE__MSG_REMOVE_DEVICE;
return connection_send_msg(ei, &msg);
return ei_proto_send_remove(ei, device);
}
static int
@ -347,17 +226,7 @@ connection_send_rel(struct ei *ei, struct ei_device *device,
ei->state == EI_STATE_DISCONNECTED)
return 0;
ClientMessage msg = CLIENT_MESSAGE__INIT;
PointerRelative rel = POINTER_RELATIVE__INIT;
rel.deviceid = device->id;
rel.x = x;
rel.y = y;
msg.rel = &rel;
msg.msg_case = CLIENT_MESSAGE__MSG_REL;
return connection_send_msg(ei, &msg);
return ei_proto_send_rel(ei, device, x, y);
}
static int
@ -368,17 +237,7 @@ connection_send_button(struct ei *ei, struct ei_device *device,
ei->state == EI_STATE_DISCONNECTED)
return 0;
ClientMessage msg = CLIENT_MESSAGE__INIT;
PointerButton button = POINTER_BUTTON__INIT;
button.deviceid = device->id;
button.button = b;
button.state = is_press;
msg.button = &button;
msg.msg_case = CLIENT_MESSAGE__MSG_BUTTON;
return connection_send_msg(ei, &msg);
return ei_proto_send_button(ei, device, b, is_press);
}
static int
@ -389,17 +248,7 @@ connection_send_key(struct ei *ei, struct ei_device *device,
ei->state == EI_STATE_DISCONNECTED)
return 0;
ClientMessage msg = CLIENT_MESSAGE__INIT;
KeyboardKey key = KEYBOARD_KEY__INIT;
key.deviceid = device->id;
key.key = k;
key.state = is_press;
msg.key = &key;
msg.msg_case = CLIENT_MESSAGE__MSG_KEY;
return connection_send_msg(ei, &msg);
return ei_proto_send_key(ei, device, k, is_press);
}
static void
@ -552,97 +401,6 @@ ei_peek_event(struct ei *ei)
return ei_event_ref(e);
}
static void frame_cleanup(Frame **f) {
if (*f)
frame__free_unpacked(*f, NULL);
}
static void servermessage_cleanup(ServerMessage **m) {
if (*m)
server_message__free_unpacked(*m, NULL);
}
#define _cleanup_frame_ _cleanup_(frame_cleanup)
#define _cleanup_servermessage_ _cleanup_(servermessage_cleanup)
static const unsigned char *
next_message(const char *data, size_t *msglen, size_t *consumed)
{
/* Every message is prefixed by a fixed-length Frame message which
* contains the length of the next message. Frames are always the
* same length, so we only need to calculate the size once.
*/
static size_t framelen = 0;
if (framelen == 0) {
Frame f = FRAME__INIT;
f.length = 0xffff;
framelen = frame__get_packed_size(&f);
assert(framelen >= 5);
}
_cleanup_frame_ Frame *frame = frame__unpack(NULL, framelen, (const unsigned char *)data);
if (!frame)
return NULL;
*msglen = frame->length;
*consumed = framelen;
return (const unsigned char*)data + framelen;
}
static struct message *
connection_parse_message(const char *data, size_t len, size_t *consumed)
{
size_t headerbytes = 0;
size_t msglen = 0;
const unsigned char *msgdata = next_message(data, &msglen, &headerbytes);
_cleanup_servermessage_ ServerMessage *proto = server_message__unpack(NULL, msglen, msgdata);
if (!proto)
return NULL;
*consumed = headerbytes + msglen;
_cleanup_message_ struct message *msg = xalloc(sizeof(*msg));
bool success = true;
switch (proto->msg_case) {
case SERVER_MESSAGE__MSG_CONNECTED:
*msg = (struct message) {
.type = MESSAGE_CONNECTED,
};
break;
case SERVER_MESSAGE__MSG_DISCONNECTED:
*msg = (struct message) {
.type = MESSAGE_DISCONNECTED,
};
break;
case SERVER_MESSAGE__MSG_DEVICE_ADDED:
{
DeviceAdded *a = proto->device_added;
*msg = (struct message) {
.type = MESSAGE_DEVICE_ADDED,
.added.deviceid = a->deviceid,
.added.capabilities = a->capabilities,
};
}
break;
case SERVER_MESSAGE__MSG_DEVICE_REMOVED:
{
DeviceRemoved *r = proto->device_removed;
*msg = (struct message) {
.type = MESSAGE_DEVICE_REMOVED,
.removed.deviceid = r->deviceid,
};
}
break;
default:
success = false;
break;
}
return success ? steal(&msg) : NULL;
}
static int
connection_new_handle_msg(struct ei *ei, struct message *msg)
{
@ -731,8 +489,8 @@ connection_dispatch(struct source *source, void *userdata)
if (len == 0)
break;
_cleanup_(message_freep) struct message *msg =
connection_parse_message(data, len, &consumed);
_cleanup_message_ struct message *msg =
ei_proto_parse_message(data, len, &consumed);
if (!msg) {
rc = -EBADMSG;
break;
@ -875,50 +633,12 @@ test_configure_name(const MunitParameter params[], void *user_data)
return MUNIT_OK;
}
static MunitResult
test_proto_next_message(const MunitParameter params[], void *user_data)
{
char data[64];
/* Invalid frame, rest can be random */
data[0] = 0xaa;
size_t msglen = 0xab;
size_t consumed = 0xbc;
const unsigned char *rval = next_message(data, &msglen, &consumed);
munit_assert_ptr_null(rval);
munit_assert_int(msglen, ==, 0xab);
munit_assert_int(consumed, ==, 0xbc);
/* Now try a valid one */
Frame f = FRAME__INIT;
f.length = 0xcd;
size_t framelen = frame__get_packed_size(&f);
unsigned char buf[framelen * 4];
for (int i = 0; i < 4; i++)
frame__pack(&f, buf + framelen * i);
const char *ptr = (char*)buf;
for (int i = 0; i < 4; i++) {
const unsigned char *next_frame = next_message(ptr, &msglen, &consumed);
munit_assert_ptr_equal(next_frame, buf + (i + 1) * framelen);
munit_assert_int(consumed, ==, framelen);
munit_assert_int(msglen, ==, 0xcd);
ptr += consumed;
}
return MUNIT_OK;
}
#define TEST(_func) \
{ .name = #_func, .test = _func }
static MunitTest ei_tests[] = {
TEST(test_init_unref),
TEST(test_configure_name),
TEST(test_proto_next_message),
{ NULL },
};