Add tests for hid_event_parser & serializer

This also moves the `sc_hid_event` type to a separate file, to enable
the other classes to use it without requiring the several other object
files to be linked.
pull/4556/head
Rob Wu 5 months ago
parent 332acab04c
commit 5ff9849765

@ -88,6 +88,7 @@ usb_support = get_option('usb')
if usb_support
src += [
'src/usb/aoa_hid.c',
'src/usb/hid_event.c',
'src/usb/hid_event_serializer.c',
'src/usb/hid_keyboard.c',
'src/usb/hid_mouse.c',
@ -259,6 +260,16 @@ if get_option('buildtype') == 'debug'
'tests/test_vector.c',
]],
]
if usb_support
tests += [
['test_hid_event_serializer', [
'tests/test_hid_event_serializer.c',
'src/usb/hid_event.c',
'src/usb/hid_event_serializer.c',
'src/util/memory.c',
]],
]
endif
foreach t : tests
sources = t[1] + ['src/compat.c']

@ -33,21 +33,6 @@ sc_hid_event_log(const struct sc_hid_event *event) {
free(buffer);
}
void
sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
unsigned char *buffer, uint16_t buffer_size) {
hid_event->accessory_id = accessory_id;
hid_event->buffer = buffer;
hid_event->size = buffer_size;
hid_event->ack_to_wait = SC_SEQUENCE_INVALID;
hid_event->timestamp = 0;
}
void
sc_hid_event_destroy(struct sc_hid_event *hid_event) {
free(hid_event->buffer);
}
bool
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
struct sc_acksync *acksync) {

@ -6,28 +6,12 @@
#include <libusb-1.0/libusb.h>
#include "hid_event.h"
#include "usb.h"
#include "util/acksync.h"
#include "util/thread.h"
#include "util/tick.h"
#include "util/vecdeque.h"
struct sc_hid_event {
uint16_t accessory_id;
unsigned char *buffer;
uint16_t size;
uint64_t ack_to_wait;
sc_tick timestamp; // Only used by hid_replay.c & hid_event_serializer.c
};
// Takes ownership of buffer
void
sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
unsigned char *buffer, uint16_t buffer_size);
void
sc_hid_event_destroy(struct sc_hid_event *hid_event);
struct sc_hid_event_queue SC_VECDEQUE(struct sc_hid_event);
// Forward declare sc_hidr to avoid circular dependency on hid_replay.h.

@ -0,0 +1,17 @@
#include "hid_event.h"
#include "util/acksync.h"
void
sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
unsigned char *buffer, uint16_t buffer_size) {
hid_event->accessory_id = accessory_id;
hid_event->buffer = buffer;
hid_event->size = buffer_size;
hid_event->ack_to_wait = SC_SEQUENCE_INVALID;
hid_event->timestamp = 0;
}
void
sc_hid_event_destroy(struct sc_hid_event *hid_event) {
free(hid_event->buffer);
}

@ -0,0 +1,24 @@
#ifndef SC_HID_EVENT_H
#define SC_HID_EVENT_H
#include "common.h"
#include <stdint.h>
#include "util/tick.h"
struct sc_hid_event {
uint16_t accessory_id;
unsigned char *buffer;
uint16_t size;
uint64_t ack_to_wait;
sc_tick timestamp; // Only used by hid_replay.c & hid_event_serializer.c
};
// Takes ownership of buffer
void
sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
unsigned char *buffer, uint16_t buffer_size);
void
sc_hid_event_destroy(struct sc_hid_event *hid_event);
#endif

@ -5,7 +5,7 @@
#include <stdbool.h>
#include "aoa_hid.h"
#include "hid_event.h"
#include "util/vecdeque.h"
// hid_event_parser: convert from bytes to sc_hid_event

@ -0,0 +1,290 @@
#include "common.h"
#include <assert.h>
#include "usb/hid_event_serializer.h"
static void test_hid_event_serializer(void) {
struct sc_hid_event hid_event;
uint16_t accessory_id = 1337;
unsigned char *buffer = malloc(5);
buffer[0] = '\xDE';
buffer[1] = '\xEA';
buffer[2] = '\xBE';
buffer[3] = '\xEF';
buffer[4] = '\x00';
uint16_t buffer_size = 5;
sc_hid_event_init(&hid_event, accessory_id, buffer, buffer_size);
assert(hid_event.timestamp == 0);
struct sc_hid_event_serializer hes;
sc_hid_event_serializer_init(&hes);
assert(hes.data_len == 0);
sc_hid_event_serializer_update(&hes, &hid_event);
assert(strlen("0 1337 de ea be ef 00\n") == hes.data_len);
assert(hes.data_len < hes.data_buffer_size); // Need room for NUL.
assert(!strncmp("0 1337 de ea be ef 00\n", hes.data, hes.data_len + 1));
hid_event.timestamp = 9001;
sc_hid_event_serializer_update(&hes, &hid_event);
assert(!strncmp("0 1337 de ea be ef 00\n9001 1337 de ea be ef 00\n",
hes.data, hes.data_len + 1));
sc_hid_event_serializer_mark_as_read(&hes);
assert(hes.data_len == 0);
sc_hid_event_serializer_update(&hes, &hid_event);
assert(!strncmp("9001 1337 de ea be ef 00\n", hes.data, hes.data_len + 1));
sc_hid_event_serializer_destroy(&hes);
free(buffer);
}
static void test_hid_event_serializer_only_init_and_destroy(void) {
struct sc_hid_event_serializer hes;
sc_hid_event_serializer_init(&hes);
sc_hid_event_serializer_destroy(&hes);
}
static void test_hid_event_serializer_minimum_length(void) {
struct sc_hid_event hid_event;
sc_hid_event_init(&hid_event, 2, calloc(1, 1), 1);
assert(hid_event.timestamp == 0);
struct sc_hid_event_serializer hes;
sc_hid_event_serializer_init(&hes);
sc_hid_event_serializer_update(&hes, &hid_event);
assert(hes.data_len < hes.data_buffer_size); // Need room for NUL.
assert(strlen("0 2 00\n") == hes.data_len);
assert(!strncmp("0 2 00\n", hes.data, hes.data_len + 1));
sc_hid_event_serializer_destroy(&hes);
free(hid_event.buffer);
}
static void test_hid_event_serializer_maximum_length(void) {
struct sc_hid_event hid_event;
sc_hid_event_init(&hid_event, 65535, calloc(1, 1), 1);
// As the type is a signed 64-bit integer, the largest length is its lowest
// value. It is unlikely for such a timestamp to be seen in practice.
hid_event.timestamp = -9223372036854775807; // = -(2^63-1)
struct sc_hid_event_serializer hes;
sc_hid_event_serializer_init(&hes);
sc_hid_event_serializer_update(&hes, &hid_event);
// Now perform an exact check instead of a "data_len < buffer_size". The
// minimum buffer size is carefully chosen to fit the longest values.
assert(hes.data_len + 1 == hes.data_buffer_size);
assert(hes.data_len < hes.data_buffer_size); // Need room for NUL.
assert(strlen("-9223372036854775807 65535 00\n") == hes.data_len);
assert(!strncmp("-9223372036854775807 65535 00\n", hes.data,
hes.data_len + 1));
sc_hid_event_serializer_destroy(&hes);
free(hid_event.buffer);
}
static void test_hid_event_parser(void) {
struct sc_hid_event_parser hep;
sc_hid_event_parser_init(&hep, "source_name");
const char input_str[] = "1 1023 f0 0d\n";
int input_len = strlen(input_str);
// Note: allocate just enough to hold input_str, without trailing NUL byte,
// to show that the NUL byte is not required (even though C strings will
// always end with a NUL).
char *data = malloc(input_len);
memcpy(data, input_str, input_len);
bool ok = sc_hid_event_parser_append_data(&hep, data, input_len);
assert(ok);
free(data); // Free immediately, further operations should not trigger UAF.
sc_hid_event_parser_parse_all_data(&hep);
assert(sc_hid_event_parser_has_next(&hep));
struct sc_hid_event *parsed_event = sc_hid_event_parser_get_next(&hep);
assert(parsed_event);
assert(!sc_hid_event_parser_has_error(&hep));
assert(!sc_hid_event_parser_has_unparsed_data(&hep));
assert(parsed_event->timestamp == 1);
assert(parsed_event->accessory_id == 1023);
assert(parsed_event->size == 2);
assert(!strncmp((const char*)parsed_event->buffer, "\xf0\x0d", 2));
// Clean up.
sc_hid_event_destroy(parsed_event);
free(parsed_event);
// Another one, partial.
ok = sc_hid_event_parser_append_data(&hep, "7", 1);
assert(ok);
sc_hid_event_parser_parse_all_data(&hep);
assert(!sc_hid_event_parser_has_next(&hep));
parsed_event = sc_hid_event_parser_get_next(&hep);
assert(!parsed_event); // Incomplete.
assert(!sc_hid_event_parser_has_error(&hep));
assert(sc_hid_event_parser_has_unparsed_data(&hep));
// Append part of the original line (minus '\n')
ok = sc_hid_event_parser_append_data(&hep, input_str, input_len - 1);
assert(ok);
// Append the new ending.
ok = sc_hid_event_parser_append_data(&hep, " DE ED\n", strlen(" DE ED\n"));
assert(ok);
// The event is not seen until sc_hid_event_parser_parse_all_data() is run:
parsed_event = sc_hid_event_parser_get_next(&hep);
assert(!parsed_event);
sc_hid_event_parser_parse_all_data(&hep);
parsed_event = sc_hid_event_parser_get_next(&hep);
assert(parsed_event);
assert(!sc_hid_event_parser_has_error(&hep));
assert(!sc_hid_event_parser_has_unparsed_data(&hep));
assert(parsed_event->timestamp == 71);
assert(parsed_event->accessory_id == 1023);
assert(parsed_event->size == 4);
assert(!strncmp((const char*)parsed_event->buffer, "\xf0\x0d\xde\xed", 4));
sc_hid_event_parser_destroy(&hep);
// Clean up once more, now after hep has been destroyed to prove that the
// parsed_event outlives the parser.
sc_hid_event_destroy(parsed_event);
free(parsed_event);
}
static void test_hid_event_parser_only_init_and_destroy(void) {
struct sc_hid_event_parser hep;
sc_hid_event_parser_init(&hep, "source_name");
sc_hid_event_parser_destroy(&hep);
}
static void test_hid_event_parser_reject_null_in_input(void) {
struct sc_hid_event_parser hep;
sc_hid_event_parser_init(&hep, "invalid_embedded_nulls");
const size_t input_len = 14;
char input_with_nul[15] = {0};
memcpy(input_with_nul + 1, "1 1023 f0 0d\n", 14);
// Note: data is not empty but data_len is 0, so the \x00 should be ignored.
bool ok = sc_hid_event_parser_append_data(&hep, input_with_nul, 0);
assert(ok); // No data to append, all right!
sc_hid_event_parser_parse_all_data(&hep);
assert(!sc_hid_event_parser_get_next(&hep));
assert(!sc_hid_event_parser_has_error(&hep));
assert(!sc_hid_event_parser_has_unparsed_data(&hep));
ok = sc_hid_event_parser_append_data(&hep, input_with_nul, input_len);
assert(!ok); // Invalid due to null.
// The error is not propagated until sc_hid_event_parser_parse_all_data():
assert(!sc_hid_event_parser_has_error(&hep));
// The "has unparsed data" status is immediately updated.
assert(sc_hid_event_parser_has_unparsed_data(&hep));
sc_hid_event_parser_parse_all_data(&hep);
// Confirm that once an error is reached, that parsing fails too.
assert(!sc_hid_event_parser_get_next(&hep));
assert(sc_hid_event_parser_has_error(&hep));
assert(sc_hid_event_parser_has_unparsed_data(&hep));
sc_hid_event_parser_destroy(&hep);
}
static void test_hid_event_parser_invalid_data(void) {
struct sc_hid_event_parser hep;
sc_hid_event_parser_init(&hep, "invalid_source_data");
bool ok = sc_hid_event_parser_append_data(&hep, "", 0);
assert(ok); // No data to append, all right!
sc_hid_event_parser_parse_all_data(&hep);
assert(!sc_hid_event_parser_get_next(&hep));
assert(!sc_hid_event_parser_has_error(&hep));
assert(!sc_hid_event_parser_has_unparsed_data(&hep));
ok = sc_hid_event_parser_append_data(&hep, "", 0);
assert(ok); // No data to append, still all right!
sc_hid_event_parser_parse_all_data(&hep);
assert(!sc_hid_event_parser_get_next(&hep));
assert(!sc_hid_event_parser_has_error(&hep));
assert(!sc_hid_event_parser_has_unparsed_data(&hep));
ok = sc_hid_event_parser_append_data(&hep, "Clearly bogus\n", 14);
assert(ok); // Garbage accepted - append does not validate.
sc_hid_event_parser_parse_all_data(&hep);
assert(!sc_hid_event_parser_get_next(&hep));
assert(sc_hid_event_parser_has_error(&hep));
assert(sc_hid_event_parser_has_unparsed_data(&hep));
ok = sc_hid_event_parser_append_data(&hep, "", 0);
assert(!ok); // No more data accepted when garbage is encountered.
sc_hid_event_parser_destroy(&hep);
}
static void test_hid_event_parser_skips_comments_and_lines(void) {
struct sc_hid_event_parser hep;
sc_hid_event_parser_init(&hep, "source_name");
bool ok = sc_hid_event_parser_append_data(&hep, "\n\n\n\n\n", 5);
assert(ok);
assert(hep.line == 1); // Not parsed yet.
sc_hid_event_parser_parse_all_data(&hep);
assert(hep.line == 6); // Skipped all blank lines.
assert(!sc_hid_event_parser_get_next(&hep));
assert(!sc_hid_event_parser_has_error(&hep));
assert(!sc_hid_event_parser_has_unparsed_data(&hep));
ok = sc_hid_event_parser_append_data(&hep, "#", 1);
assert(ok);
sc_hid_event_parser_parse_all_data(&hep);
assert(hep.line == 6); // Line not changed.
assert(!sc_hid_event_parser_get_next(&hep));
assert(!sc_hid_event_parser_has_error(&hep));
assert(sc_hid_event_parser_has_unparsed_data(&hep));
ok = sc_hid_event_parser_append_data(&hep, "xxx\n", 4);
assert(ok);
assert(hep.line == 6); // Not parsed yet.
sc_hid_event_parser_parse_all_data(&hep);
assert(hep.line == 7); // Line consumed & ignored.
assert(!sc_hid_event_parser_get_next(&hep));
assert(!sc_hid_event_parser_has_error(&hep));
assert(!sc_hid_event_parser_has_unparsed_data(&hep));
ok = sc_hid_event_parser_append_data(&hep, "#\n", 2);
assert(ok);
sc_hid_event_parser_parse_all_data(&hep);
assert(hep.line == 8); // # immediately followed by \n is also ignored.
assert(!sc_hid_event_parser_get_next(&hep));
assert(!sc_hid_event_parser_has_error(&hep));
assert(!sc_hid_event_parser_has_unparsed_data(&hep));
sc_hid_event_parser_destroy(&hep);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_hid_event_serializer();
test_hid_event_serializer_only_init_and_destroy();
test_hid_event_serializer_minimum_length();
test_hid_event_serializer_maximum_length();
test_hid_event_parser();
test_hid_event_parser_only_init_and_destroy();
test_hid_event_parser_reject_null_in_input();
test_hid_event_parser_invalid_data();
test_hid_event_parser_skips_comments_and_lines();
return 0;
}
Loading…
Cancel
Save