Implement DER decoding for BOOLEAN, INTEGER and UTF8String in Client
This commit is contained in:
parent
4b22bd726f
commit
5df58e9d28
@ -1,4 +1,64 @@
|
|||||||
namespace StudySystemClient.Der {
|
namespace StudySystemClient.Der {
|
||||||
|
public errordomain DecodeError {
|
||||||
|
INCOMPLETE,
|
||||||
|
INVALID_CONTENT,
|
||||||
|
UNKNOWN_TYPE,
|
||||||
|
}
|
||||||
|
|
||||||
|
private const uint BASE_HEADER_SIZE = 2;
|
||||||
|
|
||||||
|
public static Datum decode(uint8[] bytes) 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);
|
||||||
|
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];
|
||||||
|
return decode_datum(bytes[0], content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint decode_length(uint8[] bytes, ref uint header_size)
|
||||||
|
throws DecodeError {
|
||||||
|
if ((bytes[1] & 0x80) != 0) {
|
||||||
|
var length_size = bytes[1] & 0x7f;
|
||||||
|
if (BASE_HEADER_SIZE + length_size > bytes.length) {
|
||||||
|
throw new DecodeError.INCOMPLETE(
|
||||||
|
"Length with size %u but only %u bytes available",
|
||||||
|
length_size, bytes.length - BASE_HEADER_SIZE);
|
||||||
|
}
|
||||||
|
var length = 0;
|
||||||
|
for (int i = 0; i < length_size; ++i)
|
||||||
|
length = length << 8 | bytes[BASE_HEADER_SIZE + i];
|
||||||
|
header_size = BASE_HEADER_SIZE + length_size;
|
||||||
|
return length;
|
||||||
|
} else {
|
||||||
|
header_size = BASE_HEADER_SIZE;
|
||||||
|
return bytes[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Datum decode_datum(uint8 type, uint8[] content)
|
||||||
|
throws DecodeError {
|
||||||
|
switch (type) {
|
||||||
|
case Boolean.TYPE:
|
||||||
|
return new Boolean.from_content(content);
|
||||||
|
case Integer.TYPE:
|
||||||
|
return new Integer.from_content(content);
|
||||||
|
case Utf8String.TYPE:
|
||||||
|
return new Utf8String.from_content(content);
|
||||||
|
default:
|
||||||
|
throw new DecodeError.UNKNOWN_TYPE("Unsupported type: %02x",
|
||||||
|
type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public abstract class Datum {
|
public abstract class Datum {
|
||||||
internal uint8 type;
|
internal uint8 type;
|
||||||
internal uint8[] content;
|
internal uint8[] content;
|
||||||
@ -33,20 +93,56 @@ namespace StudySystemClient.Der {
|
|||||||
public class Boolean : Datum {
|
public class Boolean : Datum {
|
||||||
internal const uint8 TYPE = 0x01;
|
internal const uint8 TYPE = 0x01;
|
||||||
|
|
||||||
|
public bool value { get; private set; }
|
||||||
|
|
||||||
public Boolean(bool val) {
|
public Boolean(bool val) {
|
||||||
type = TYPE;
|
type = TYPE;
|
||||||
content = new uint8[] { val ? 0xff : 0x00 };
|
content = new uint8[] { val ? 0xff : 0x00 };
|
||||||
|
value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Boolean.from_content(uint8[] bytes) throws DecodeError {
|
||||||
|
type = TYPE;
|
||||||
|
content = bytes;
|
||||||
|
value = decode_bool(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool decode_bool(uint8[] bytes)
|
||||||
|
throws DecodeError {
|
||||||
|
if (bytes.length != 1) {
|
||||||
|
throw new DecodeError.INVALID_CONTENT(
|
||||||
|
"Invalid length for boolean: %u", bytes.length);
|
||||||
|
}
|
||||||
|
switch (bytes[0]) {
|
||||||
|
case 0xff:
|
||||||
|
return true;
|
||||||
|
case 0x00:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
throw new DecodeError.INVALID_CONTENT(
|
||||||
|
"Invalid value of boolean: %02x", bytes[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Integer : Datum {
|
public class Integer : Datum {
|
||||||
internal const uint8 TYPE = 0x02;
|
internal const uint8 TYPE = 0x02;
|
||||||
|
private const uint8 MAX_BYTES = 8;
|
||||||
|
|
||||||
|
public int64 value { get; private set; }
|
||||||
|
|
||||||
public Integer(int64 val) {
|
public Integer(int64 val) {
|
||||||
type = TYPE;
|
type = TYPE;
|
||||||
content = encode_int64(val);
|
content = encode_int64(val);
|
||||||
|
value = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal Integer.from_content(uint8[] bytes) throws DecodeError {
|
||||||
|
type = TYPE;
|
||||||
|
content = bytes;
|
||||||
|
value = decode_int64(content);
|
||||||
|
}
|
||||||
|
|
||||||
private static uint64 twos_complement(uint64 x) {
|
private static uint64 twos_complement(uint64 x) {
|
||||||
return ~x + 1;
|
return ~x + 1;
|
||||||
}
|
}
|
||||||
@ -77,14 +173,52 @@ namespace StudySystemClient.Der {
|
|||||||
|
|
||||||
return buffer.data;
|
return buffer.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int64 decode_int64(uint8[] bytes) throws DecodeError {
|
||||||
|
if (bytes.length > MAX_BYTES) {
|
||||||
|
throw new DecodeError.INVALID_CONTENT(
|
||||||
|
"int64 too small for %u bytes", bytes.length);
|
||||||
|
}
|
||||||
|
var negative = (bytes[0] & 0x80) != 0;
|
||||||
|
var val = decode_start_val(negative, bytes.length);
|
||||||
|
foreach (var byte in bytes)
|
||||||
|
val = val << 8 | byte;
|
||||||
|
return negative ? -(int64)twos_complement(val) : (int64)val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint64 decode_start_val(bool negative, uint length)
|
||||||
|
{
|
||||||
|
if (!negative)
|
||||||
|
return 0;
|
||||||
|
var val = 0;
|
||||||
|
for (uint i = 0; i < MAX_BYTES - length; ++i)
|
||||||
|
val = val << 8 | 0xff;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Utf8String : Datum {
|
public class Utf8String : Datum {
|
||||||
internal const uint8 TYPE = 0x0c;
|
internal const uint8 TYPE = 0x0c;
|
||||||
|
|
||||||
|
public string value { get; private set; }
|
||||||
|
|
||||||
public Utf8String(string val) {
|
public Utf8String(string val) {
|
||||||
type = TYPE;
|
type = TYPE;
|
||||||
content = val.data;
|
content = val.data;
|
||||||
|
value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Utf8String.from_content(uint8[] bytes) {
|
||||||
|
type = TYPE;
|
||||||
|
content = bytes;
|
||||||
|
value = decode_string(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,19 @@
|
|||||||
using StudySystemClient.Der;
|
using StudySystemClient.Der;
|
||||||
|
|
||||||
static bool bytes_equal(uint8[] expected, uint8[] actual)
|
static bool bytes_equal(uint8[] expected, uint8[] actual) {
|
||||||
{
|
|
||||||
if (expected.length != actual.length)
|
if (expected.length != actual.length)
|
||||||
return false;
|
return false;
|
||||||
return Memory.cmp(expected, actual, expected.length) == 0;
|
return Memory.cmp(expected, actual, expected.length) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static string bytes_to_string(uint8[] bytes)
|
static string bytes_to_string(uint8[] bytes) {
|
||||||
{
|
|
||||||
var s = "";
|
var s = "";
|
||||||
foreach (var byte in bytes)
|
foreach (var byte in bytes)
|
||||||
s += "%02x".printf(byte);
|
s += "%02x".printf(byte);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_encode(Datum datum, uint8[] expected)
|
static void test_encode(Datum datum, uint8[] expected) {
|
||||||
{
|
|
||||||
var bytes = datum.encode();
|
var bytes = datum.encode();
|
||||||
if (!bytes_equal(expected, bytes)) {
|
if (!bytes_equal(expected, bytes)) {
|
||||||
Test.message("Encoding is incorrect: expected %s got %s",
|
Test.message("Encoding is incorrect: expected %s got %s",
|
||||||
@ -26,6 +23,72 @@ static void test_encode(Datum datum, uint8[] expected)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_decode_boolean(uint8[] bytes, bool expected) {
|
||||||
|
Boolean boolean;
|
||||||
|
try {
|
||||||
|
boolean = decode(bytes) as Boolean;
|
||||||
|
} catch (DecodeError err) {
|
||||||
|
Test.message("Decoding failed: %s", err.message);
|
||||||
|
Test.fail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boolean == null) {
|
||||||
|
Test.message("Bytes were not decoded as a BOOLEAN");
|
||||||
|
Test.fail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (boolean.value != expected) {
|
||||||
|
Test.message(@"Expected $expected got $(boolean.value)");
|
||||||
|
Test.fail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_decode_integer(uint8[] bytes, int64 expected) {
|
||||||
|
Integer integer;
|
||||||
|
try {
|
||||||
|
integer = decode(bytes) as Integer;
|
||||||
|
} catch (DecodeError err) {
|
||||||
|
Test.message("Decoding failed: %s", err.message);
|
||||||
|
Test.fail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (integer == null) {
|
||||||
|
Test.message("Bytes were not decoded as a INTEGER");
|
||||||
|
Test.fail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (integer.value != expected) {
|
||||||
|
Test.message(@"Expected $expected got $(integer.value)");
|
||||||
|
Test.fail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (utf8string == null) {
|
||||||
|
Test.message("Bytes were not decoded as a UTF8String");
|
||||||
|
Test.fail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (utf8string.value != expected) {
|
||||||
|
Test.message(@"Expected $expected got $(utf8string.value)");
|
||||||
|
Test.fail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void main(string[] args) {
|
void main(string[] args) {
|
||||||
Test.init(ref args);
|
Test.init(ref args);
|
||||||
|
|
||||||
@ -90,6 +153,60 @@ void main(string[] args) {
|
|||||||
Memory.set(expected[3:], 0x78, 128);
|
Memory.set(expected[3:], 0x78, 128);
|
||||||
test_encode(new Utf8String(string.nfill(128, 'x')), expected);
|
test_encode(new Utf8String(string.nfill(128, 'x')), expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Decoding
|
||||||
|
*/
|
||||||
|
|
||||||
|
Test.add_func("/decode/boolean/true", () => {
|
||||||
|
test_decode_boolean({0x01, 0x01, 0xff}, true);
|
||||||
|
});
|
||||||
|
Test.add_func("/decode/boolean/false", () => {
|
||||||
|
test_decode_boolean({0x01, 0x01, 0x00}, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Test.add_func("/decode/integer/small/42", () => {
|
||||||
|
test_decode_integer({0x02, 0x01, 0x2a}, 42);
|
||||||
|
});
|
||||||
|
Test.add_func("/decode/integer/large/1337", () => {
|
||||||
|
test_decode_integer({0x02, 0x02, 0x05, 0x39}, 1337);
|
||||||
|
});
|
||||||
|
Test.add_func("/decode/integer/sign/128", () => {
|
||||||
|
test_decode_integer({0x02, 0x02, 0x00, 0x80}, 128);
|
||||||
|
});
|
||||||
|
Test.add_func("/decode/integer/sign/0xbeef", () => {
|
||||||
|
test_decode_integer({0x02, 0x03, 0x00, 0xbe, 0xef}, 0xbeef);
|
||||||
|
});
|
||||||
|
Test.add_func("/decode/integer/sign/-128", () => {
|
||||||
|
test_decode_integer({0x02, 0x01, 0x80}, -128);
|
||||||
|
});
|
||||||
|
Test.add_func("/decode/integer/sign/-1337", () => {
|
||||||
|
test_decode_integer({0x02, 0x02, 0xfa, 0xc7}, -1337);
|
||||||
|
});
|
||||||
|
|
||||||
|
Test.add_func("/decode/utf8string/short/foo", () => {
|
||||||
|
test_decode_utf8string({0x0c, 0x03, 0x66, 0x6f, 0x6f}, "foo");
|
||||||
|
});
|
||||||
|
Test.add_func("/decode/utf8string/short/bar", () => {
|
||||||
|
test_decode_utf8string({0x0c, 0x03, 0x62, 0x61, 0x72}, "bar");
|
||||||
|
});
|
||||||
|
Test.add_func("/decode/utf8string/long/x300", () => {
|
||||||
|
var bytes = new uint8[304];
|
||||||
|
bytes[0] = 0x0c;
|
||||||
|
bytes[1] = 0x82;
|
||||||
|
bytes[2] = 0x01;
|
||||||
|
bytes[3] = 0x2c;
|
||||||
|
Memory.set(bytes[4:], 0x78, 300);
|
||||||
|
test_decode_utf8string(bytes, string.nfill(300, 'x'));
|
||||||
|
});
|
||||||
|
Test.add_func("/decode/utf8string/long/x128", () => {
|
||||||
|
var bytes = new uint8[131];
|
||||||
|
bytes[0] = 0x0c;
|
||||||
|
bytes[1] = 0x81;
|
||||||
|
bytes[2] = 0x80;
|
||||||
|
Memory.set(bytes[3:], 0x78, 128);
|
||||||
|
test_decode_utf8string(bytes, string.nfill(128, 'x'));
|
||||||
|
});
|
||||||
|
|
||||||
Test.run();
|
Test.run();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user