From 5ff9849765f0d02a1f25d5506793e5d2fa891edf Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Sun, 31 Dec 2023 19:59:33 +0100 Subject: [PATCH] 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. --- app/meson.build | 11 + app/src/usb/aoa_hid.c | 15 -- app/src/usb/aoa_hid.h | 18 +- app/src/usb/hid_event.c | 17 ++ app/src/usb/hid_event.h | 24 +++ app/src/usb/hid_event_serializer.h | 2 +- app/tests/test_hid_event_serializer.c | 290 ++++++++++++++++++++++++++ 7 files changed, 344 insertions(+), 33 deletions(-) create mode 100644 app/src/usb/hid_event.c create mode 100644 app/src/usb/hid_event.h create mode 100644 app/tests/test_hid_event_serializer.c diff --git a/app/meson.build b/app/meson.build index 04b9dfde..d2ae6dd3 100644 --- a/app/meson.build +++ b/app/meson.build @@ -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'] diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index 01150d0b..5b1eb6a8 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.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) { diff --git a/app/src/usb/aoa_hid.h b/app/src/usb/aoa_hid.h index 27f91c4c..95b07918 100644 --- a/app/src/usb/aoa_hid.h +++ b/app/src/usb/aoa_hid.h @@ -6,28 +6,12 @@ #include +#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. diff --git a/app/src/usb/hid_event.c b/app/src/usb/hid_event.c new file mode 100644 index 00000000..330108a5 --- /dev/null +++ b/app/src/usb/hid_event.c @@ -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); +} diff --git a/app/src/usb/hid_event.h b/app/src/usb/hid_event.h new file mode 100644 index 00000000..2b052cc0 --- /dev/null +++ b/app/src/usb/hid_event.h @@ -0,0 +1,24 @@ +#ifndef SC_HID_EVENT_H +#define SC_HID_EVENT_H + +#include "common.h" + +#include +#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 diff --git a/app/src/usb/hid_event_serializer.h b/app/src/usb/hid_event_serializer.h index b792803b..b8d3d63f 100644 --- a/app/src/usb/hid_event_serializer.h +++ b/app/src/usb/hid_event_serializer.h @@ -5,7 +5,7 @@ #include -#include "aoa_hid.h" +#include "hid_event.h" #include "util/vecdeque.h" // hid_event_parser: convert from bytes to sc_hid_event diff --git a/app/tests/test_hid_event_serializer.c b/app/tests/test_hid_event_serializer.c new file mode 100644 index 00000000..9e805f09 --- /dev/null +++ b/app/tests/test_hid_event_serializer.c @@ -0,0 +1,290 @@ +#include "common.h" + +#include + +#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; +}