From 4b22bd726f26dd37e09966bafbbb551b560f4ec5 Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Sun, 23 Feb 2025 16:06:09 +0000 Subject: [PATCH] Implement DER encoding for BOOLEAN, INTEGER and UTF8String in Client --- client/meson.build | 1 + client/src/der.vala | 90 +++++++++++++++++++++++++++++++++++ client/src/meson.build | 1 + client/tests/der_tests.vala | 95 +++++++++++++++++++++++++++++++++++++ client/tests/meson.build | 8 ++++ 5 files changed, 195 insertions(+) create mode 100644 client/src/der.vala create mode 100644 client/tests/der_tests.vala create mode 100644 client/tests/meson.build diff --git a/client/meson.build b/client/meson.build index 0924001..2326bff 100644 --- a/client/meson.build +++ b/client/meson.build @@ -11,3 +11,4 @@ add_project_arguments('-w', language: 'c') gtk_dep = dependency('gtk4') subdir('src') +subdir('tests') diff --git a/client/src/der.vala b/client/src/der.vala new file mode 100644 index 0000000..15c4d5f --- /dev/null +++ b/client/src/der.vala @@ -0,0 +1,90 @@ +namespace StudySystemClient.Der { + public abstract class Datum { + internal uint8 type; + internal uint8[] content; + + public uint8[] encode() { + var buffer = new ByteArray(); + + buffer.append({type}); + if (content.length >= 0x80) { + var length_bytes = encode_length(content.length); + buffer.append({0x80 | (uint8)length_bytes.length}); + buffer.append(length_bytes); + } else { + buffer.append({(uint8)content.length}); + } + buffer.append(content); + + return buffer.data; + } + + private static uint8[] encode_length(uint length) { + var buffer = new ByteArray(); + int shift = 0; + while (length >> (shift + 8) != 0) + shift += 8; + for (; shift >= 0; shift -= 8) + buffer.append({(uint8)(length >> shift)}); + return buffer.data; + } + } + + public class Boolean : Datum { + internal const uint8 TYPE = 0x01; + + public Boolean(bool val) { + type = TYPE; + content = new uint8[] { val ? 0xff : 0x00 }; + } + } + + public class Integer : Datum { + internal const uint8 TYPE = 0x02; + + public Integer(int64 val) { + type = TYPE; + content = encode_int64(val); + } + + private static uint64 twos_complement(uint64 x) { + return ~x + 1; + } + + private static int min_bits(bool negative, uint64 x) { + int n = 0; + if (negative) { + while ((x >> (n + 8) & 0xff) != 0xff) + n += 8; + } else { + while (x >> (n + 8) > 0) + n += 8; + } + return n; + } + + private static uint8[] encode_int64(int64 val) { + var negative = val < 0; + var uval = negative ? twos_complement(val.abs()) : val; + var shift = min_bits(negative, uval); + + var buffer = new ByteArray(); + for (; shift >= 0; shift -= 8) + buffer.append({(uint8)(uval >> shift)}); + + if (!negative && (buffer.data[0] & 0x80) != 0) + buffer.prepend({0x00}); + + return buffer.data; + } + } + + public class Utf8String : Datum { + internal const uint8 TYPE = 0x0c; + + public Utf8String(string val) { + type = TYPE; + content = val.data; + } + } +} diff --git a/client/src/meson.build b/client/src/meson.build index b5795aa..570b59c 100644 --- a/client/src/meson.build +++ b/client/src/meson.build @@ -11,6 +11,7 @@ lib = library( 'study-system-client', sources: files( 'connection.vala', + 'der.vala', 'main_window.vala', ), dependencies: [gtk_dep], diff --git a/client/tests/der_tests.vala b/client/tests/der_tests.vala new file mode 100644 index 0000000..412b745 --- /dev/null +++ b/client/tests/der_tests.vala @@ -0,0 +1,95 @@ +using StudySystemClient.Der; + +static bool bytes_equal(uint8[] expected, uint8[] actual) +{ + if (expected.length != actual.length) + return false; + return Memory.cmp(expected, actual, expected.length) == 0; +} + +static string bytes_to_string(uint8[] bytes) +{ + var s = ""; + foreach (var byte in bytes) + s += "%02x".printf(byte); + return s; +} + +static void test_encode(Datum datum, uint8[] expected) +{ + var bytes = datum.encode(); + if (!bytes_equal(expected, bytes)) { + Test.message("Encoding is incorrect: expected %s got %s", + bytes_to_string(expected), bytes_to_string(bytes)); + Test.fail(); + return; + } +} + +void main(string[] args) { + Test.init(ref args); + + /* + * Encoding + */ + + Test.add_func("/encode/boolean/true", () => { + test_encode(new Boolean(true), {0x01, 0x01, 0xff}); + }); + Test.add_func("/encode/boolean/false", () => { + test_encode(new Boolean(false), {0x01, 0x01, 0x00}); + }); + + Test.add_func("/encode/integer/small/0", () => { + test_encode(new Integer(0), {0x02, 0x01, 0x00}); + }); + Test.add_func("/encode/integer/small/5", () => { + test_encode(new Integer(5), {0x02, 0x01, 0x05}); + }); + Test.add_func("/encode/integer/small/42", () => { + test_encode(new Integer(42), {0x02, 0x01, 0x2a}); + }); + Test.add_func("/encode/integer/large/1337", () => { + test_encode(new Integer(1337), {0x02, 0x02, 0x05, 0x39}); + }); + Test.add_func("/encode/integer/sign/128", () => { + test_encode(new Integer(128), {0x02, 0x02, 0x00, 0x80}); + }); + Test.add_func("/encode/integer/sign/0xbeef", () => { + test_encode(new Integer(0xbeef), {0x02, 0x03, 0x00, 0xbe, 0xef}); + }); + Test.add_func("/encode/integer/sign/-128", () => { + test_encode(new Integer(-128), {0x02, 0x01, 0x80}); + }); + Test.add_func("/encode/integer/sign/-1337", () => { + test_encode(new Integer(-1337), {0x02, 0x02, 0xfa, 0xc7}); + }); + + Test.add_func("/encode/utf8string/short/foo", () => { + test_encode(new Utf8String("foo"), + {0x0c, 0x03, 0x66, 0x6f, 0x6f}); + }); + Test.add_func("/encode/utf8string/short/bar", () => { + test_encode(new Utf8String("bar"), + {0x0c, 0x03, 0x62, 0x61, 0x72}); + }); + Test.add_func("/encode/utf8string/long/x300", () => { + var expected = new uint8[304]; + expected[0] = 0x0c; + expected[1] = 0x82; + expected[2] = 0x01; + expected[3] = 0x2c; + Memory.set(expected[4:], 0x78, 300); + test_encode(new Utf8String(string.nfill(300, 'x')), expected); + }); + Test.add_func("/encode/utf8string/long/x128", () => { + var expected = new uint8[131]; + expected[0] = 0x0c; + expected[1] = 0x81; + expected[2] = 0x80; + Memory.set(expected[3:], 0x78, 128); + test_encode(new Utf8String(string.nfill(128, 'x')), expected); + }); + + Test.run(); +} diff --git a/client/tests/meson.build b/client/tests/meson.build new file mode 100644 index 0000000..9401079 --- /dev/null +++ b/client/tests/meson.build @@ -0,0 +1,8 @@ +test( + 'DER tests', + executable( + 'der_tests', + 'der_tests.vala', + dependencies: [lib_dep, gtk_dep] + ) +)