Implement SEQUENCE encoding and decoding in client

This commit is contained in:
Camden Dixie O'Brien 2025-02-23 19:05:48 +00:00
parent 5df58e9d28
commit ef9e578e25
2 changed files with 105 additions and 17 deletions

View File

@ -7,24 +7,26 @@ namespace StudySystemClient.Der {
private const uint BASE_HEADER_SIZE = 2; 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) { if (bytes.length < BASE_HEADER_SIZE) {
throw new DecodeError.INCOMPLETE( throw new DecodeError.INCOMPLETE(
"Message is fewer than %u bytes", BASE_HEADER_SIZE); "Message is fewer than %u bytes", BASE_HEADER_SIZE);
} }
uint header_size = 0; uint header_size;
var length = decode_length(bytes, ref header_size); var length = decode_length(bytes, out header_size);
if (header_size + length > bytes.length) { if (header_size + length > bytes.length) {
throw new DecodeError.INCOMPLETE( throw new DecodeError.INCOMPLETE(
"Length %u but only %u bytes available", length, "Length %u but only %u bytes available", length,
bytes.length - header_size); bytes.length - header_size);
} }
var content = bytes[header_size:header_size + length]; var content = bytes[header_size:header_size + length];
size = header_size + length;
return decode_datum(bytes[0], content); 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 { throws DecodeError {
if ((bytes[1] & 0x80) != 0) { if ((bytes[1] & 0x80) != 0) {
var length_size = bytes[1] & 0x7f; var length_size = bytes[1] & 0x7f;
@ -53,6 +55,8 @@ namespace StudySystemClient.Der {
return new Integer.from_content(content); return new Integer.from_content(content);
case Utf8String.TYPE: case Utf8String.TYPE:
return new Utf8String.from_content(content); return new Utf8String.from_content(content);
case Sequence.TYPE:
return new Sequence.from_content(content);
default: default:
throw new DecodeError.UNKNOWN_TYPE("Unsupported type: %02x", throw new DecodeError.UNKNOWN_TYPE("Unsupported type: %02x",
type); type);
@ -214,11 +218,48 @@ namespace StudySystemClient.Der {
value = decode_string(bytes); 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]; var buffer = new uint8[bytes.length + 1];
Memory.copy(buffer, bytes, bytes.length); Memory.copy(buffer, bytes, bytes.length);
buffer[bytes.length] = 0; buffer[bytes.length] = 0;
return (string)buffer; 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<Datum>();
uint offset = 0;
uint size;
while (offset < bytes.length) {
elems.add(decode(bytes[offset:], out size));
offset += size;
}
return elems.data;
}
}
} }

View File

@ -1,3 +1,4 @@
using StudySystemClient;
using StudySystemClient.Der; using StudySystemClient.Der;
static bool bytes_equal(uint8[] expected, uint8[] actual) { 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) { static void test_decode_integer(uint8[] bytes, int64 expected) {
Integer integer; Datum datum;
try { 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) { } catch (DecodeError err) {
Test.message("Decoding failed: %s", err.message); Test.message("Decoding failed: %s", err.message);
Test.fail(); Test.fail();
return; return;
} }
test_utf8string_value(expected, datum);
}
static void test_integer_value(int64 expected, Datum datum) {
var integer = datum as Integer;
if (integer == null) { if (integer == null) {
Test.message("Bytes were not decoded as a INTEGER"); Test.message("Bytes were not decoded as a INTEGER");
Test.fail(); Test.fail();
@ -67,16 +84,8 @@ static void test_decode_integer(uint8[] bytes, int64 expected) {
} }
} }
static void test_decode_utf8string(uint8[] bytes, string expected) { static void test_utf8string_value(string expected, Datum datum) {
Utf8String utf8string; var utf8string = datum as Utf8String;
try {
utf8string = decode(bytes) as Utf8String;
} catch (DecodeError err) {
Test.message("Decoding failed: %s", err.message);
Test.fail();
return;
}
if (utf8string == null) { if (utf8string == null) {
Test.message("Bytes were not decoded as a UTF8String"); Test.message("Bytes were not decoded as a UTF8String");
Test.fail(); Test.fail();
@ -154,6 +163,15 @@ void main(string[] args) {
test_encode(new Utf8String(string.nfill(128, 'x')), expected); 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 * Decoding
*/ */
@ -208,5 +226,34 @@ void main(string[] args) {
test_decode_utf8string(bytes, string.nfill(128, 'x')); 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(); Test.run();
} }