diff --git a/client/src/der.vala b/client/src/der.vala index d572147..bfdcfd9 100644 --- a/client/src/der.vala +++ b/client/src/der.vala @@ -7,24 +7,26 @@ namespace StudySystemClient.Der { private const uint BASE_HEADER_SIZE = 2; - public static Datum decode(uint8[] bytes) throws DecodeError { + public static Datum decode(uint8[] bytes, out uint? size = null) + throws DecodeError { if (bytes.length < BASE_HEADER_SIZE) { throw new DecodeError.INCOMPLETE( "Message is fewer than %u bytes", BASE_HEADER_SIZE); } - uint header_size = 0; - var length = decode_length(bytes, ref header_size); + uint header_size; + var length = decode_length(bytes, out header_size); if (header_size + length > bytes.length) { throw new DecodeError.INCOMPLETE( "Length %u but only %u bytes available", length, bytes.length - header_size); } var content = bytes[header_size:header_size + length]; + size = header_size + length; return decode_datum(bytes[0], content); } - private static uint decode_length(uint8[] bytes, ref uint header_size) + private static uint decode_length(uint8[] bytes, out uint header_size) throws DecodeError { if ((bytes[1] & 0x80) != 0) { var length_size = bytes[1] & 0x7f; @@ -53,6 +55,8 @@ namespace StudySystemClient.Der { return new Integer.from_content(content); case Utf8String.TYPE: return new Utf8String.from_content(content); + case Sequence.TYPE: + return new Sequence.from_content(content); default: throw new DecodeError.UNKNOWN_TYPE("Unsupported type: %02x", type); @@ -214,11 +218,48 @@ namespace StudySystemClient.Der { value = decode_string(bytes); } - public static string decode_string(uint8[] bytes) { + private static string decode_string(uint8[] bytes) { var buffer = new uint8[bytes.length + 1]; Memory.copy(buffer, bytes, bytes.length); buffer[bytes.length] = 0; return (string)buffer; } } + + public class Sequence : Datum { + internal const uint8 TYPE = 0x30; + + public Datum[] value { get; private set; } + + public Sequence(Datum[] val) { + type = TYPE; + content = encode_array(val); + value = val; + } + + internal Sequence.from_content(uint8[] bytes) throws DecodeError { + type = TYPE; + content = bytes; + value = decode_array(bytes); + } + + private static uint8[] encode_array(Datum[] val) { + var buffer = new ByteArray(); + foreach (var datum in val) + buffer.append(datum.encode()); + return buffer.data; + } + + private static Datum[] decode_array(uint8[] bytes) + throws DecodeError { + var elems = new GenericArray(); + uint offset = 0; + uint size; + while (offset < bytes.length) { + elems.add(decode(bytes[offset:], out size)); + offset += size; + } + return elems.data; + } + } } diff --git a/client/tests/der_tests.vala b/client/tests/der_tests.vala index cf3d3ce..ec9433a 100644 --- a/client/tests/der_tests.vala +++ b/client/tests/der_tests.vala @@ -1,3 +1,4 @@ +using StudySystemClient; using StudySystemClient.Der; static bool bytes_equal(uint8[] expected, uint8[] actual) { @@ -46,15 +47,31 @@ static void test_decode_boolean(uint8[] bytes, bool expected) { } static void test_decode_integer(uint8[] bytes, int64 expected) { - Integer integer; + Datum datum; try { - integer = decode(bytes) as Integer; + datum = decode(bytes); + } catch (DecodeError err) { + Test.message("Decoding failed: %s", err.message); + Test.fail(); + return; + } + test_integer_value(expected, datum); +} + +static void test_decode_utf8string(uint8[] bytes, string expected) { + Datum datum; + try { + datum = decode(bytes); } catch (DecodeError err) { Test.message("Decoding failed: %s", err.message); Test.fail(); return; } + test_utf8string_value(expected, datum); +} +static void test_integer_value(int64 expected, Datum datum) { + var integer = datum as Integer; if (integer == null) { Test.message("Bytes were not decoded as a INTEGER"); Test.fail(); @@ -67,16 +84,8 @@ static void test_decode_integer(uint8[] bytes, int64 expected) { } } -static void test_decode_utf8string(uint8[] bytes, string expected) { - Utf8String utf8string; - try { - utf8string = decode(bytes) as Utf8String; - } catch (DecodeError err) { - Test.message("Decoding failed: %s", err.message); - Test.fail(); - return; - } - +static void test_utf8string_value(string expected, Datum datum) { + var utf8string = datum as Utf8String; if (utf8string == null) { Test.message("Bytes were not decoded as a UTF8String"); Test.fail(); @@ -154,6 +163,15 @@ void main(string[] args) { test_encode(new Utf8String(string.nfill(128, 'x')), expected); }); + Test.add_func("/encode/sequence/foo,42", () => { + var sequence = new Der.Sequence( + {new Utf8String("foo"), new Integer(42)}); + var expected = new uint8[] { + 0x30, 0x08, 0x0c, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x01, 0x2a + }; + test_encode(sequence, expected); + }); + /* * Decoding */ @@ -208,5 +226,34 @@ void main(string[] args) { test_decode_utf8string(bytes, string.nfill(128, 'x')); }); + Test.add_func("/decode/sequence/foo,42", () => { + var bytes = new uint8[] { + 0x30, 0x08, 0x0c, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x01, 0x2a + }; + var expected = 2; + Der.Sequence sequence; + try { + sequence = decode(bytes) as Der.Sequence; + } catch (DecodeError err) { + Test.message("Decoding failed: %s", err.message); + Test.fail(); + return; + } + if (sequence == null) { + Test.message("Bytes were not decoded as a SEQUENCE"); + Test.fail(); + return; + } + Datum[] elems = sequence.value; + if (elems.length != expected) { + Test.message( + @"Expected $expected elements, got $(elems.length)"); + Test.fail(); + return; + } + test_utf8string_value("foo", elems[0]); + test_integer_value(42, elems[1]); + }); + Test.run(); }