331 lines
7.8 KiB
Vala
331 lines
7.8 KiB
Vala
namespace StudySystemClient.Der {
|
|
public errordomain DecodeError {
|
|
INCOMPLETE,
|
|
INVALID_CONTENT,
|
|
UNKNOWN_TYPE,
|
|
}
|
|
|
|
private const uint BASE_HEADER_SIZE = 2;
|
|
private const uint8 MAX_INT64_BYTES = 8;
|
|
|
|
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;
|
|
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, out 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 {
|
|
if ((type & ~Choice.ID_MASK) == Choice.BASE_TYPE)
|
|
return new Choice.from_content(type, content);
|
|
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);
|
|
case Sequence.TYPE:
|
|
return new Sequence.from_content(content);
|
|
case Null.TYPE:
|
|
return new Null.from_content(content);
|
|
case Enumerated.TYPE:
|
|
return new Enumerated.from_content(content);
|
|
default:
|
|
throw new DecodeError.UNKNOWN_TYPE("Unsupported type: %02x",
|
|
type);
|
|
}
|
|
}
|
|
|
|
public abstract class Datum {
|
|
protected uint8 type;
|
|
protected 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 bool value { get; private set; }
|
|
|
|
public Boolean(bool val) {
|
|
type = TYPE;
|
|
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 {
|
|
internal const uint8 TYPE = 0x02;
|
|
|
|
public int64 value { get; private set; }
|
|
|
|
public Integer(int64 val) {
|
|
type = TYPE;
|
|
content = encode_int64(val);
|
|
value = val;
|
|
}
|
|
|
|
internal Integer.from_content(uint8[] bytes) throws DecodeError {
|
|
type = TYPE;
|
|
content = bytes;
|
|
value = decode_int64(content);
|
|
}
|
|
}
|
|
|
|
public class Utf8String : Datum {
|
|
internal const uint8 TYPE = 0x0c;
|
|
|
|
public string value { get; private set; }
|
|
|
|
public Utf8String(string val) {
|
|
type = TYPE;
|
|
content = val.data;
|
|
value = val;
|
|
}
|
|
|
|
public Utf8String.from_content(uint8[] bytes) {
|
|
type = TYPE;
|
|
content = bytes;
|
|
value = decode_string(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<Datum>();
|
|
uint offset = 0;
|
|
uint size;
|
|
while (offset < bytes.length) {
|
|
elems.add(decode(bytes[offset:], out size));
|
|
offset += size;
|
|
}
|
|
return elems.data;
|
|
}
|
|
}
|
|
|
|
public class Choice : Datum {
|
|
internal const uint8 BASE_TYPE = 0xa0;
|
|
internal const uint8 ID_MASK = 0x1f;
|
|
|
|
public int id { get; private set; }
|
|
public Datum value { get; private set; }
|
|
|
|
public Choice(int id, Datum val) {
|
|
type = BASE_TYPE | id;
|
|
content = val.encode();
|
|
this.id = id;
|
|
value = val;
|
|
}
|
|
|
|
internal Choice.from_content(uint8 type, uint8[] bytes)
|
|
throws DecodeError {
|
|
this.type = type;
|
|
content = bytes;
|
|
id = type & ID_MASK;
|
|
value = decode(bytes);
|
|
}
|
|
}
|
|
|
|
public class Null : Datum {
|
|
internal const uint8 TYPE = 0x05;
|
|
|
|
public Null() {
|
|
type = TYPE;
|
|
content = new uint8[] {};
|
|
}
|
|
|
|
internal Null.from_content(uint8[] bytes) throws DecodeError {
|
|
if (bytes.length != 0) {
|
|
throw new DecodeError.INVALID_CONTENT(
|
|
"Non-empty content for NULL");
|
|
}
|
|
type = TYPE;
|
|
content = bytes;
|
|
}
|
|
}
|
|
|
|
public class Enumerated : Datum {
|
|
internal const uint8 TYPE = 0x0a;
|
|
|
|
public int64 value { get; private set; }
|
|
|
|
public Enumerated(int64 val) {
|
|
type = TYPE;
|
|
content = encode_int64(val);
|
|
value = val;
|
|
}
|
|
|
|
internal Enumerated.from_content(uint8[] bytes) throws DecodeError {
|
|
type = TYPE;
|
|
content = bytes;
|
|
value = decode_int64(content);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
private static int64 decode_int64(uint8[] bytes) throws DecodeError {
|
|
if (bytes.length > MAX_INT64_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_INT64_BYTES - length; ++i)
|
|
val = val << 8 | 0xff;
|
|
return val;
|
|
}
|
|
}
|