You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5173 lines
136 KiB
5173 lines
136 KiB
// Written in the D programming language.
|
|
|
|
/**
|
|
* MessagePack serializer and deserializer implementation.
|
|
*
|
|
* MessagePack is a binary-based serialization specification.
|
|
*
|
|
* Example:
|
|
* -----
|
|
* auto data = tuple("MessagePack!", [1, 2], true);
|
|
*
|
|
* auto serialized = pack(data);
|
|
*
|
|
* // ...
|
|
*
|
|
* typeof(data) deserialized;
|
|
*
|
|
* unpack(serialized, deserialized);
|
|
*
|
|
* assert(data == deserialized);
|
|
* -----
|
|
*
|
|
* See_Also:
|
|
* $(LINK2 http://msgpack.org/, The MessagePack Project)$(BR)
|
|
* $(LINK2 http://wiki.msgpack.org/display/MSGPACK/Design+of+Serialization, MessagePack Design concept)$(BR)
|
|
* $(LINK2 http://wiki.msgpack.org/display/MSGPACK/Format+specification, MessagePack data format)
|
|
*
|
|
* Copyright: Copyright Masahiro Nakagawa 2010-.
|
|
* License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
|
|
* Authors: Masahiro Nakagawa
|
|
*/
|
|
module bio.bam.thirdparty.msgpack;
|
|
|
|
import std.array;
|
|
import std.exception;
|
|
import std.range;
|
|
import std.stdio;
|
|
import std.traits;
|
|
import std.typecons;
|
|
import std.typetuple;
|
|
|
|
// for RefBuffer
|
|
version(Posix)
|
|
{
|
|
import core.sys.posix.sys.uio : iovec;
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* from core.sys.posix.sys.uio.iovec for compatibility with posix.
|
|
*/
|
|
struct iovec
|
|
{
|
|
void* iov_base;
|
|
size_t iov_len;
|
|
}
|
|
}
|
|
|
|
// for Converting Endian using ntohs and ntohl;
|
|
version(Windows)
|
|
{
|
|
import core.stdc.windows.winsock;
|
|
}
|
|
else
|
|
{
|
|
import core.sys.posix.arpa.inet;
|
|
}
|
|
|
|
version(EnableReal)
|
|
{
|
|
enum EnableReal = true;
|
|
}
|
|
else
|
|
{
|
|
enum EnableReal = false;
|
|
}
|
|
|
|
static if (real.sizeof == double.sizeof) {
|
|
// for 80bit real inter-operation on non-x86 CPU
|
|
version = NonX86;
|
|
|
|
import std.numeric;
|
|
}
|
|
|
|
version(unittest) import std.file, core.stdc.string;
|
|
|
|
|
|
@trusted:
|
|
|
|
|
|
public:
|
|
|
|
|
|
// Convenient functions
|
|
|
|
|
|
/**
|
|
* Serializes $(D_PARAM args).
|
|
*
|
|
* Assumes single object if the length of $(D_PARAM args) == 1,
|
|
* otherwise array object.
|
|
*
|
|
* Params:
|
|
* args = the contents to serialize.
|
|
*
|
|
* Returns:
|
|
* a serialized data.
|
|
*/
|
|
ubyte[] pack(bool withFieldName = false, Args...)(in Args args)
|
|
{
|
|
auto packer = Packer(withFieldName);
|
|
|
|
static if (Args.length == 1)
|
|
packer.pack(args[0]);
|
|
else
|
|
packer.packArray(args);
|
|
|
|
return packer.stream.data;
|
|
}
|
|
|
|
|
|
unittest
|
|
{
|
|
auto serialized = pack(false);
|
|
|
|
assert(serialized[0] == Format.FALSE);
|
|
|
|
auto deserialized = unpack(pack(1, true, "Foo"));
|
|
|
|
assert(deserialized.type == Value.Type.array);
|
|
assert(deserialized.via.array[0].type == Value.Type.unsigned);
|
|
assert(deserialized.via.array[1].type == Value.Type.boolean);
|
|
assert(deserialized.via.array[2].type == Value.Type.raw);
|
|
}
|
|
|
|
|
|
/**
|
|
* Deserializes $(D_PARAM buffer) using stream deserializer.
|
|
*
|
|
* Params:
|
|
* buffer = the buffer to deserialize.
|
|
*
|
|
* Returns:
|
|
* a $(D Unpacked) contains deserialized object.
|
|
*
|
|
* Throws:
|
|
* UnpackException if deserialization doesn't succeed.
|
|
*/
|
|
Unpacked unpack(in ubyte[] buffer)
|
|
{
|
|
auto unpacker = StreamingUnpacker(buffer);
|
|
|
|
if (!unpacker.execute())
|
|
throw new UnpackException("Deserialization failure");
|
|
|
|
return unpacker.unpacked;
|
|
}
|
|
|
|
|
|
/**
|
|
* Deserializes $(D_PARAM buffer) using direct-conversion deserializer.
|
|
*
|
|
* Assumes single object if the length of $(D_PARAM args) == 1,
|
|
* otherwise array object.
|
|
*
|
|
* Params:
|
|
* buffer = the buffer to deserialize.
|
|
* args = the references of values to assign.
|
|
*/
|
|
void unpack(bool withFieldName = false, Args...)(in ubyte[] buffer, ref Args args)
|
|
{
|
|
auto unpacker = Unpacker(buffer, buffer.length, withFieldName);
|
|
|
|
static if (Args.length == 1)
|
|
unpacker.unpack(args[0]);
|
|
else
|
|
unpacker.unpackArray(args);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return value version
|
|
*/
|
|
Type unpack(Type, bool withFieldName = false)(in ubyte[] buffer)
|
|
{
|
|
auto unpacker = Unpacker(buffer, buffer.length, withFieldName);
|
|
|
|
Type result;
|
|
unpacker.unpack(result);
|
|
return result;
|
|
}
|
|
|
|
|
|
unittest
|
|
{
|
|
{ // stream
|
|
auto result = unpack(pack(false));
|
|
|
|
assert(result.via.boolean == false);
|
|
}
|
|
{ // direct conversion
|
|
Tuple!(uint, string) result;
|
|
Tuple!(uint, string) test = tuple(1, "Hi!");
|
|
|
|
unpack(pack(test), result);
|
|
assert(result == test);
|
|
|
|
test.field[0] = 2;
|
|
test.field[1] = "Hey!";
|
|
unpack(pack(test.field[0], test.field[1]), result.field[0], result.field[1]);
|
|
assert(result == test);
|
|
}
|
|
{ // return value direct conversion
|
|
Tuple!(uint, string) test = tuple(1, "Hi!");
|
|
|
|
auto data = pack(test);
|
|
assert(data.unpack!(Tuple!(uint, string)) == test);
|
|
}
|
|
{ // serialize object as a Map
|
|
static class C
|
|
{
|
|
int num;
|
|
|
|
this(int num) { this.num = num; }
|
|
}
|
|
|
|
auto test = new C(10);
|
|
auto result = new C(100);
|
|
|
|
unpack!(true)(pack!(true)(test), result);
|
|
assert(result.num == 10, "Unpacking with field names failed");
|
|
}
|
|
}
|
|
|
|
|
|
unittest
|
|
{
|
|
// unittest for https://github.com/msgpack/msgpack-d/issues/8
|
|
foreach (Type; TypeTuple!(byte, short, int, long)) {
|
|
foreach (i; [-33, -20, -1, 0, 1, 20, 33]) {
|
|
Type a = cast(Type)i;
|
|
Type b;
|
|
unpack(pack(a), b);
|
|
assert(a == b);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* $(D MessagePackException) is a root Exception for MessagePack related operation.
|
|
*/
|
|
class MessagePackException : Exception
|
|
{
|
|
pure this(string message)
|
|
{
|
|
super(message);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Attribute for specifying non pack/unpack field.
|
|
* This is an alternative approach of MessagePackable mixin.
|
|
*
|
|
* Example:
|
|
* -----
|
|
* struct S
|
|
* {
|
|
* int num;
|
|
* // Packer/Unpacker ignores this field;
|
|
* @nonPacked string str;
|
|
* }
|
|
* -----
|
|
*/
|
|
struct nonPacked {}
|
|
|
|
template isPackedField(alias field)
|
|
{
|
|
enum isPackedField = (staticIndexOf!(nonPacked, __traits(getAttributes, field)) == -1) && (!isSomeFunction!(typeof(field)));
|
|
}
|
|
|
|
|
|
// Serializing routines
|
|
|
|
|
|
/**
|
|
* $(D Packer) is a $(D MessagePack) serializer
|
|
*
|
|
* Example:
|
|
* -----
|
|
* auto packer = packer(Appender!(ubyte[])());
|
|
*
|
|
* packer.packArray(false, 100, 1e-10, null);
|
|
*
|
|
* stdout.rawWrite(packer.buffer.data);
|
|
* -----
|
|
*
|
|
* NOTE:
|
|
* Current implementation can't deal with a circular reference.
|
|
* If you try to serialize a object that has circular reference, runtime raises 'Stack Overflow'.
|
|
*/
|
|
struct PackerImpl(Stream) if (isOutputRange!(Stream, ubyte) && isOutputRange!(Stream, ubyte[]))
|
|
{
|
|
private:
|
|
static @system
|
|
{
|
|
alias void delegate(ref PackerImpl, void*) PackHandler;
|
|
PackHandler[TypeInfo] packHandlers;
|
|
|
|
public void registerHandler(T, alias Handler)()
|
|
{
|
|
packHandlers[typeid(T)] = delegate(ref PackerImpl packer, void* obj) {
|
|
Handler(packer, *cast(T*)obj);
|
|
};
|
|
}
|
|
}
|
|
|
|
enum size_t Offset = 1; // type-information offset
|
|
|
|
Stream stream_; // the stream to write
|
|
ubyte[Offset + RealSize] store_; // stores serialized value
|
|
bool withFieldName_;
|
|
|
|
|
|
public:
|
|
/**
|
|
* Constructs a packer with $(D_PARAM stream).
|
|
*
|
|
* Params:
|
|
* stream = the stream to write.
|
|
* withFieldName = serialize class / struct with field name
|
|
*/
|
|
this(Stream stream, bool withFieldName = false)
|
|
{
|
|
stream_ = stream;
|
|
withFieldName_ = withFieldName;
|
|
}
|
|
|
|
|
|
/**
|
|
* Constructs a packer with $(D_PARAM withFieldName).
|
|
*
|
|
* Params:
|
|
* withFieldName = serialize class / struct with field name
|
|
*/
|
|
this(bool withFieldName)
|
|
{
|
|
withFieldName_ = withFieldName;
|
|
}
|
|
|
|
|
|
/**
|
|
* Forwards to stream.
|
|
*
|
|
* Returns:
|
|
* the stream.
|
|
*/
|
|
@property @safe
|
|
nothrow ref Stream stream()
|
|
{
|
|
return stream_;
|
|
}
|
|
|
|
|
|
/**
|
|
* Serializes argument and writes to stream.
|
|
*
|
|
* If the argument is the pointer type, dereferences the pointer and serializes pointed value.
|
|
* -----
|
|
* int a = 10;
|
|
* int* b = &b;
|
|
*
|
|
* packer.pack(b); // serializes 10, not address of a
|
|
* -----
|
|
* Serializes nil if the argument of nullable type is null.
|
|
*
|
|
* NOTE:
|
|
* MessagePack doesn't define $(D_KEYWORD real) type format.
|
|
* Don't serialize $(D_KEYWORD real) if you communicate with other languages.
|
|
* Transfer $(D_KEYWORD double) serialization if $(D_KEYWORD real) on your environment equals $(D_KEYWORD double).
|
|
*
|
|
* Params:
|
|
* value = the content to serialize.
|
|
*
|
|
* Returns:
|
|
* self, i.e. for method chaining.
|
|
*/
|
|
ref PackerImpl pack(T)(in T value) if (is(Unqual!T == bool))
|
|
{
|
|
if (value)
|
|
stream_.put(Format.TRUE);
|
|
else
|
|
stream_.put(Format.FALSE);
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref PackerImpl pack(T)(in T value) if (isUnsigned!T && !is(Unqual!T == enum))
|
|
{
|
|
// ulong < ulong is slower than uint < uint
|
|
static if (!is(Unqual!T == ulong)) {
|
|
enum Bits = T.sizeof * 8;
|
|
|
|
if (value < (1 << 8)) {
|
|
if (value < (1 << 7)) {
|
|
// fixnum
|
|
stream_.put(take8from!Bits(value));
|
|
} else {
|
|
// uint 8
|
|
store_[0] = Format.UINT8;
|
|
store_[1] = take8from!Bits(value);
|
|
stream_.put(store_[0..Offset + ubyte.sizeof]);
|
|
}
|
|
} else {
|
|
if (value < (1 << 16)) {
|
|
// uint 16
|
|
const temp = convertEndianTo!16(value);
|
|
|
|
store_[0] = Format.UINT16;
|
|
*cast(ushort*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + ushort.sizeof]);
|
|
} else {
|
|
// uint 32
|
|
const temp = convertEndianTo!32(value);
|
|
|
|
store_[0] = Format.UINT32;
|
|
*cast(uint*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + uint.sizeof]);
|
|
}
|
|
}
|
|
} else {
|
|
if (value < (1UL << 8)) {
|
|
if (value < (1UL << 7)) {
|
|
// fixnum
|
|
stream_.put(take8from!64(value));
|
|
} else {
|
|
// uint 8
|
|
store_[0] = Format.UINT8;
|
|
store_[1] = take8from!64(value);
|
|
stream_.put(store_[0..Offset + ubyte.sizeof]);
|
|
}
|
|
} else {
|
|
if (value < (1UL << 16)) {
|
|
// uint 16
|
|
const temp = convertEndianTo!16(value);
|
|
|
|
store_[0] = Format.UINT16;
|
|
*cast(ushort*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + ushort.sizeof]);
|
|
} else if (value < (1UL << 32)){
|
|
// uint 32
|
|
const temp = convertEndianTo!32(value);
|
|
|
|
store_[0] = Format.UINT32;
|
|
*cast(uint*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + uint.sizeof]);
|
|
} else {
|
|
// uint 64
|
|
const temp = convertEndianTo!64(value);
|
|
|
|
store_[0] = Format.UINT64;
|
|
*cast(ulong*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + ulong.sizeof]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref PackerImpl pack(T)(in T value) if (isSigned!T && isIntegral!T && !is(Unqual!T == enum))
|
|
{
|
|
// long < long is slower than int < int
|
|
static if (!is(Unqual!T == long)) {
|
|
enum Bits = T.sizeof * 8;
|
|
|
|
if (value < -(1 << 5)) {
|
|
if (value < -(1 << 15)) {
|
|
// int 32
|
|
const temp = convertEndianTo!32(value);
|
|
|
|
store_[0] = Format.INT32;
|
|
*cast(int*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + int.sizeof]);
|
|
} else if (value < -(1 << 7)) {
|
|
// int 16
|
|
const temp = convertEndianTo!16(value);
|
|
|
|
store_[0] = Format.INT16;
|
|
*cast(short*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + short.sizeof]);
|
|
} else {
|
|
// int 8
|
|
store_[0] = Format.INT8;
|
|
store_[1] = take8from!Bits(value);
|
|
stream_.put(store_[0..Offset + byte.sizeof]);
|
|
}
|
|
} else if (value < (1 << 7)) {
|
|
// fixnum
|
|
stream_.put(take8from!Bits(value));
|
|
} else {
|
|
if (value < (1 << 8)) {
|
|
// uint 8
|
|
store_[0] = Format.UINT8;
|
|
store_[1] = take8from!Bits(value);
|
|
stream_.put(store_[0..Offset + ubyte.sizeof]);
|
|
} else if (value < (1 << 16)) {
|
|
// uint 16
|
|
const temp = convertEndianTo!16(value);
|
|
|
|
store_[0] = Format.UINT16;
|
|
*cast(ushort*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + ushort.sizeof]);
|
|
} else {
|
|
// uint 32
|
|
const temp = convertEndianTo!32(value);
|
|
|
|
store_[0] = Format.UINT32;
|
|
*cast(uint*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + uint.sizeof]);
|
|
}
|
|
}
|
|
} else {
|
|
if (value < -(1L << 5)) {
|
|
if (value < -(1L << 15)) {
|
|
if (value < -(1L << 31)) {
|
|
// int 64
|
|
const temp = convertEndianTo!64(value);
|
|
|
|
store_[0] = Format.INT64;
|
|
*cast(long*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + long.sizeof]);
|
|
} else {
|
|
// int 32
|
|
const temp = convertEndianTo!32(value);
|
|
|
|
store_[0] = Format.INT32;
|
|
*cast(int*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + int.sizeof]);
|
|
}
|
|
} else {
|
|
if (value < -(1L << 7)) {
|
|
// int 16
|
|
const temp = convertEndianTo!16(value);
|
|
|
|
store_[0] = Format.INT16;
|
|
*cast(short*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + short.sizeof]);
|
|
} else {
|
|
// int 8
|
|
store_[0] = Format.INT8;
|
|
store_[1] = take8from!64(value);
|
|
stream_.put(store_[0..Offset + byte.sizeof]);
|
|
}
|
|
}
|
|
} else if (value < (1L << 7)) {
|
|
// fixnum
|
|
stream_.put(take8from!64(value));
|
|
} else {
|
|
if (value < (1L << 16)) {
|
|
if (value < (1L << 8)) {
|
|
// uint 8
|
|
store_[0] = Format.UINT8;
|
|
store_[1] = take8from!64(value);
|
|
stream_.put(store_[0..Offset + ubyte.sizeof]);
|
|
} else {
|
|
// uint 16
|
|
const temp = convertEndianTo!16(value);
|
|
|
|
store_[0] = Format.UINT16;
|
|
*cast(ushort*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + ushort.sizeof]);
|
|
}
|
|
} else {
|
|
if (value < (1L << 32)) {
|
|
// uint 32
|
|
const temp = convertEndianTo!32(value);
|
|
|
|
store_[0] = Format.UINT32;
|
|
*cast(uint*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + uint.sizeof]);
|
|
} else {
|
|
// uint 64
|
|
const temp = convertEndianTo!64(value);
|
|
|
|
store_[0] = Format.UINT64;
|
|
*cast(ulong*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + ulong.sizeof]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref PackerImpl pack(T)(in T value) if (isFloatingPoint!T && !is(Unqual!T == enum))
|
|
{
|
|
static if (is(Unqual!T == float)) {
|
|
const temp = convertEndianTo!32(_f(value).i);
|
|
|
|
store_[0] = Format.FLOAT;
|
|
*cast(uint*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + uint.sizeof]);
|
|
} else static if (is(Unqual!T == double)) {
|
|
const temp = convertEndianTo!64(_d(value).i);
|
|
|
|
store_[0] = Format.DOUBLE;
|
|
*cast(ulong*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + ulong.sizeof]);
|
|
} else {
|
|
static if ((real.sizeof > double.sizeof) && EnableReal) {
|
|
store_[0] = Format.REAL;
|
|
const temp = _r(value);
|
|
const fraction = convertEndianTo!64(temp.fraction);
|
|
const exponent = convertEndianTo!16(temp.exponent);
|
|
|
|
*cast(Unqual!(typeof(fraction))*)&store_[Offset] = fraction;
|
|
*cast(Unqual!(typeof(exponent))*)&store_[Offset + fraction.sizeof] = exponent;
|
|
stream_.put(store_[0..$]);
|
|
} else { // Non-x86 CPUs, real type equals double type.
|
|
pack(cast(double)value);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref PackerImpl pack(T)(in T value) if (is(Unqual!T == enum))
|
|
{
|
|
pack(cast(OriginalType!T)value);
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// Overload for pack(null) for 2.057 or later
|
|
static if (!is(typeof(null) == void*))
|
|
{
|
|
ref PackerImpl pack(T)(in T value) if (is(Unqual!T == typeof(null)))
|
|
{
|
|
return packNil();
|
|
}
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref PackerImpl pack(T)(in T value) if (isPointer!T)
|
|
{
|
|
static if (is(Unqual!T == void*)) { // for pack(null) for 2.056 or earlier
|
|
enforce(value is null, "Can't serialize void type");
|
|
stream_.put(Format.NIL);
|
|
} else {
|
|
if (value is null)
|
|
stream_.put(Format.NIL);
|
|
else
|
|
pack(mixin(AsteriskOf!T ~ "value"));
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref PackerImpl pack(T)(in T array) if (isArray!T)
|
|
{
|
|
alias typeof(T.init[0]) U;
|
|
|
|
/*
|
|
* Serializes raw type-information to stream.
|
|
*/
|
|
void beginRaw(in size_t length)
|
|
{
|
|
if (length < 32) {
|
|
const ubyte temp = Format.RAW | cast(ubyte)length;
|
|
stream_.put(take8from(temp));
|
|
} else if (length < 65536) {
|
|
const temp = convertEndianTo!16(length);
|
|
|
|
store_[0] = Format.RAW16;
|
|
*cast(ushort*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + ushort.sizeof]);
|
|
} else {
|
|
const temp = convertEndianTo!32(length);
|
|
|
|
store_[0] = Format.RAW32;
|
|
*cast(uint*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + uint.sizeof]);
|
|
}
|
|
}
|
|
|
|
if (array.empty)
|
|
return packNil();
|
|
|
|
// Raw bytes
|
|
static if (isByte!(U) || isSomeChar!(U)) {
|
|
ubyte[] raw = cast(ubyte[])array;
|
|
|
|
beginRaw(raw.length);
|
|
stream_.put(raw);
|
|
} else {
|
|
beginArray(array.length);
|
|
foreach (elem; array)
|
|
pack(elem);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref PackerImpl pack(T)(in T array) if (isAssociativeArray!T)
|
|
{
|
|
if (array is null)
|
|
return packNil();
|
|
|
|
beginMap(array.length);
|
|
foreach (key, value; array) {
|
|
pack(key);
|
|
pack(value);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref PackerImpl pack(Types...)(auto ref const Types objects) if (Types.length > 1)
|
|
{
|
|
foreach (i, T; Types)
|
|
pack(objects[i]);
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/**
|
|
* Serializes $(D_PARAM object) and writes to stream.
|
|
*
|
|
* Calling $(D toMsgpack) if $(D_KEYWORD class) and $(D_KEYWORD struct) implement $(D toMsgpack) method. $(D toMsgpack) signature is:
|
|
* -----
|
|
* void toMsgpack(Packer)(ref Packer packer) const
|
|
* -----
|
|
* This method serializes all members of T object if $(D_KEYWORD class) and $(D_KEYWORD struct) don't implement $(D toMsgpack).
|
|
*
|
|
* An object that doesn't implement $(D toMsgpack) is serialized to Array type.
|
|
* -----
|
|
* packer.pack(tuple(true, 1, "Hi!")) // -> '[true, 1, "Hi!"]', not 'ture, 1, "Hi!"'
|
|
*
|
|
* struct Foo
|
|
* {
|
|
* int num = 10;
|
|
* string msg = "D!";
|
|
* }
|
|
* packer.pack(Foo()); // -> '[10, "D!"]'
|
|
*
|
|
* class Base
|
|
* {
|
|
* bool flag = true;
|
|
* }
|
|
* class Derived : Base
|
|
* {
|
|
* double = 0.5f;
|
|
* }
|
|
* packer.pack(new Derived()); // -> '[true, 0.5f]'
|
|
* -----
|
|
*
|
|
* Params:
|
|
* object = the content to serialize.
|
|
*
|
|
* Returns:
|
|
* self, i.e. for method chaining.
|
|
*/
|
|
ref PackerImpl pack(T)(in T object) if (is(Unqual!T == class))
|
|
{
|
|
if (object is null)
|
|
return packNil();
|
|
|
|
static if (hasMember!(T, "toMsgpack"))
|
|
{
|
|
static if (__traits(compiles, { T t; t.toMsgpack(this, withFieldName_); })) {
|
|
object.toMsgpack(this, withFieldName_);
|
|
} else static if (__traits(compiles, { T t; t.toMsgpack(this); })) { // backward compatible
|
|
object.toMsgpack(this);
|
|
} else {
|
|
static assert(0, "Failed to invoke 'toMsgpack' on type '" ~ Unqual!T.stringof ~ "'");
|
|
}
|
|
} else {
|
|
if (auto handler = object.classinfo in packHandlers) {
|
|
(*handler)(this, cast(void*)&object);
|
|
return this;
|
|
}
|
|
if (T.classinfo !is object.classinfo) {
|
|
throw new MessagePackException("Can't pack derived class through reference to base class.");
|
|
}
|
|
|
|
alias SerializingClasses!(T) Classes;
|
|
|
|
immutable memberNum = SerializingMemberNumbers!(Classes);
|
|
if (withFieldName_)
|
|
beginMap(memberNum);
|
|
else
|
|
beginArray(memberNum);
|
|
|
|
foreach (Class; Classes) {
|
|
Class obj = cast(Class)object;
|
|
if (withFieldName_) {
|
|
foreach (i, f ; obj.tupleof) {
|
|
static if (isPackedField!(Class.tupleof[i])) {
|
|
pack(getFieldName!(Class, i));
|
|
pack(f);
|
|
}
|
|
}
|
|
} else {
|
|
foreach (i, f ; obj.tupleof) {
|
|
static if (isPackedField!(Class.tupleof[i]))
|
|
pack(f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
@trusted
|
|
ref PackerImpl pack(T)(auto ref T object) if (is(Unqual!T == struct))
|
|
{
|
|
static if (hasMember!(T, "toMsgpack"))
|
|
{
|
|
static if (__traits(compiles, { T t; t.toMsgpack(this, withFieldName_); })) {
|
|
object.toMsgpack(this, withFieldName_);
|
|
} else static if (__traits(compiles, { T t; t.toMsgpack(this); })) { // backward compatible
|
|
object.toMsgpack(this);
|
|
} else {
|
|
static assert(0, "Failed to invoke 'toMsgpack' on type '" ~ Unqual!T.stringof ~ "'");
|
|
}
|
|
} else static if (isTuple!T) {
|
|
beginArray(object.field.length);
|
|
foreach (f; object.field)
|
|
pack(f);
|
|
} else { // simple struct
|
|
if (auto handler = typeid(T) in packHandlers) {
|
|
(*handler)(this, cast(void*)&object);
|
|
return this;
|
|
}
|
|
|
|
immutable memberNum = SerializingMemberNumbers!(T);
|
|
if (withFieldName_)
|
|
beginMap(memberNum);
|
|
else
|
|
beginArray(memberNum);
|
|
|
|
if (withFieldName_) {
|
|
foreach (i, f; object.tupleof) {
|
|
static if (isPackedField!(T.tupleof[i]) && __traits(compiles, { pack(f); }))
|
|
{
|
|
pack(getFieldName!(T, i));
|
|
pack(f);
|
|
}
|
|
}
|
|
} else {
|
|
foreach (i, f; object.tupleof) {
|
|
static if (isPackedField!(T.tupleof[i]) && __traits(compiles, { pack(f); }))
|
|
pack(f);
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/**
|
|
* Serializes the arguments as container to stream.
|
|
*
|
|
* -----
|
|
* packer.packArray(true, 1); // -> [true, 1]
|
|
* packer.packMap("Hi", 100); // -> ["Hi":100]
|
|
* -----
|
|
*
|
|
* In packMap, the number of arguments must be even.
|
|
*
|
|
* Params:
|
|
* objects = the contents to serialize.
|
|
*
|
|
* Returns:
|
|
* self, i.e. for method chaining.
|
|
*/
|
|
ref PackerImpl packArray(Types...)(auto ref const Types objects)
|
|
{
|
|
beginArray(Types.length);
|
|
foreach (i, T; Types)
|
|
pack(objects[i]);
|
|
//pack(objects); // slow :(
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref PackerImpl packMap(Types...)(auto ref const Types objects)
|
|
{
|
|
static assert(Types.length % 2 == 0, "The number of arguments must be even");
|
|
|
|
beginMap(Types.length / 2);
|
|
foreach (i, T; Types)
|
|
pack(objects[i]);
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/**
|
|
* Serializes the type-information to stream.
|
|
*
|
|
* These methods don't serialize contents.
|
|
* You need to call pack method to serialize contents at your own risk.
|
|
* -----
|
|
* packer.beginArray(3).pack(true, 1); // -> [true, 1,
|
|
*
|
|
* // other operation
|
|
*
|
|
* packer.pack("Hi!"); // -> [true, 1, "Hi!"]
|
|
* -----
|
|
*
|
|
* Params:
|
|
* length = the length of container.
|
|
*
|
|
* Returns:
|
|
* self, i.e. for method chaining.
|
|
*/
|
|
ref PackerImpl beginArray(in size_t length)
|
|
{
|
|
if (length < 16) {
|
|
const ubyte temp = Format.ARRAY | cast(ubyte)length;
|
|
stream_.put(take8from(temp));
|
|
} else if (length < 65536) {
|
|
const temp = convertEndianTo!16(length);
|
|
|
|
store_[0] = Format.ARRAY16;
|
|
*cast(ushort*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + ushort.sizeof]);
|
|
} else {
|
|
const temp = convertEndianTo!32(length);
|
|
|
|
store_[0] = Format.ARRAY32;
|
|
*cast(uint*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + uint.sizeof]);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref PackerImpl beginMap(in size_t length)
|
|
{
|
|
if (length < 16) {
|
|
const ubyte temp = Format.MAP | cast(ubyte)length;
|
|
stream_.put(take8from(temp));
|
|
} else if (length < 65536) {
|
|
const temp = convertEndianTo!16(length);
|
|
|
|
store_[0] = Format.MAP16;
|
|
*cast(ushort*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + ushort.sizeof]);
|
|
} else {
|
|
const temp = convertEndianTo!32(length);
|
|
|
|
store_[0] = Format.MAP32;
|
|
*cast(uint*)&store_[Offset] = temp;
|
|
stream_.put(store_[0..Offset + uint.sizeof]);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
private:
|
|
/*
|
|
* Serializes the nil value.
|
|
*/
|
|
ref PackerImpl packNil()
|
|
{
|
|
stream_.put(Format.NIL);
|
|
return this;
|
|
}
|
|
}
|
|
|
|
|
|
/// Default serializer
|
|
alias PackerImpl!(Appender!(ubyte[])) Packer; // should be pure struct?
|
|
|
|
|
|
/**
|
|
* Register a serialization handler for $(D_PARAM T) type
|
|
*
|
|
* Example:
|
|
* -----
|
|
* registerPackHandler!(Foo, fooPackHandler);
|
|
* -----
|
|
*/
|
|
void registerPackHandler(T, alias Handler, Stream = Appender!(ubyte[]))()
|
|
{
|
|
PackerImpl!(Stream).registerHandler!(T, Handler);
|
|
}
|
|
|
|
|
|
/**
|
|
* Helper for $(D Packer) construction.
|
|
*
|
|
* Params:
|
|
* stream = the stream to write.
|
|
* withFieldName = serialize class / struct with field name
|
|
*
|
|
* Returns:
|
|
* a $(D Packer) object instantiated and initialized according to the arguments.
|
|
*/
|
|
PackerImpl!(Stream) packer(Stream)(Stream stream, bool withFieldName = false)
|
|
{
|
|
return typeof(return)(stream, withFieldName);
|
|
}
|
|
|
|
|
|
// Buffer implementations
|
|
|
|
|
|
/**
|
|
* $(D RefBuffer) is a reference stored buffer for more efficient serialization
|
|
*
|
|
* Example:
|
|
* -----
|
|
* auto packer = packer(RefBuffer(16)); // threshold is 16
|
|
*
|
|
* // packs data
|
|
*
|
|
* writev(fd, cast(void*)packer.buffer.vector.ptr, packer.buffer.vector.length);
|
|
* -----
|
|
*/
|
|
struct RefBuffer
|
|
{
|
|
private:
|
|
static struct Chunk
|
|
{
|
|
ubyte[] data; // storing serialized value
|
|
size_t used; // used size of data
|
|
}
|
|
|
|
immutable size_t Threshold;
|
|
immutable size_t ChunkSize;
|
|
|
|
// for putCopy
|
|
Chunk[] chunks_; // memory chunk for buffer
|
|
size_t index_; // index for cunrrent chunk
|
|
|
|
// for putRef
|
|
iovec[] vecList_; // reference to large data or copied data.
|
|
|
|
|
|
public:
|
|
/**
|
|
* Constructs a buffer.
|
|
*
|
|
* Params:
|
|
* threshold = the threshold of writing value or stores reference.
|
|
* chunkSize = the default size of chunk for allocation.
|
|
*/
|
|
@safe
|
|
this(in size_t threshold, in size_t chunkSize = 8192)
|
|
{
|
|
Threshold = threshold;
|
|
ChunkSize = chunkSize;
|
|
|
|
chunks_.length = 1;
|
|
chunks_[index_].data.length = chunkSize;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the buffer contents that excluding references.
|
|
*
|
|
* Returns:
|
|
* the non-contiguous copied contents.
|
|
*/
|
|
@property @safe
|
|
nothrow ubyte[] data()
|
|
{
|
|
ubyte[] result;
|
|
|
|
foreach (ref chunk; chunks_)
|
|
result ~= chunk.data[0..chunk.used];
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* Forwards to all buffer contents.
|
|
*
|
|
* Returns:
|
|
* the array of iovec struct that stores references.
|
|
*/
|
|
@property @safe
|
|
nothrow ref iovec[] vector()
|
|
{
|
|
return vecList_;
|
|
}
|
|
|
|
|
|
/**
|
|
* Writes the argument to buffer and stores the reference of writed content
|
|
* if the argument size is smaller than threshold,
|
|
* otherwise stores the reference of argument directly.
|
|
*
|
|
* Params:
|
|
* value = the content to write.
|
|
*/
|
|
@safe
|
|
void put(in ubyte value)
|
|
{
|
|
ubyte[1] values = [value];
|
|
putCopy(values);
|
|
}
|
|
|
|
|
|
/// ditto
|
|
@safe
|
|
void put(in ubyte[] value)
|
|
{
|
|
if (value.length < Threshold)
|
|
putCopy(value);
|
|
else
|
|
putRef(value);
|
|
}
|
|
|
|
|
|
private:
|
|
/*
|
|
* Stores the reference of $(D_PARAM value).
|
|
*
|
|
* Params:
|
|
* value = the content to write.
|
|
*/
|
|
@trusted
|
|
void putRef(in ubyte[] value)
|
|
{
|
|
vecList_.length += 1;
|
|
vecList_[$ - 1] = iovec(cast(void*)value.ptr, value.length);
|
|
}
|
|
|
|
|
|
/*
|
|
* Writes $(D_PARAM value) to buffer and appends to its reference.
|
|
*
|
|
* Params:
|
|
* value = the contents to write.
|
|
*/
|
|
@trusted
|
|
void putCopy(in ubyte[] value)
|
|
{
|
|
/*
|
|
* Helper for expanding new space.
|
|
*/
|
|
void expand(in size_t size)
|
|
{
|
|
const newSize = size < ChunkSize ? ChunkSize : size;
|
|
|
|
index_++;
|
|
chunks_.length = 1;
|
|
chunks_[index_].data.length = newSize;
|
|
}
|
|
|
|
const size = value.length;
|
|
|
|
// lacks current chunk?
|
|
if (chunks_[index_].data.length - chunks_[index_].used < size)
|
|
expand(size);
|
|
|
|
const base = chunks_[index_].used; // start index
|
|
auto data = chunks_[index_].data[base..base + size]; // chunk to write
|
|
|
|
data[] = value[];
|
|
chunks_[index_].used += size;
|
|
|
|
// Optimization for avoiding iovec allocation.
|
|
if (vecList_.length && data.ptr == (vecList_[$ - 1].iov_base +
|
|
vecList_[$ - 1].iov_len))
|
|
vecList_[$ - 1].iov_len += size;
|
|
else
|
|
putRef(data);
|
|
}
|
|
}
|
|
|
|
|
|
unittest
|
|
{
|
|
static assert(isOutputRange!(RefBuffer, ubyte) &&
|
|
isOutputRange!(RefBuffer, ubyte[]));
|
|
|
|
auto buffer = RefBuffer(2, 4);
|
|
|
|
ubyte[] tests = [1, 2];
|
|
foreach (v; tests)
|
|
buffer.put(v);
|
|
buffer.put(tests);
|
|
|
|
assert(buffer.data == tests, "putCopy failed");
|
|
|
|
iovec[] vector = buffer.vector;
|
|
ubyte[] result;
|
|
|
|
assert(vector.length == 2, "Optimization failed");
|
|
|
|
foreach (v; vector)
|
|
result ~= (cast(ubyte*)v.iov_base)[0..v.iov_len];
|
|
|
|
assert(result == tests ~ tests);
|
|
}
|
|
|
|
|
|
version (unittest)
|
|
{
|
|
mixin template DefinePacker()
|
|
{
|
|
Packer packer;
|
|
}
|
|
|
|
mixin template DefineDictionalPacker()
|
|
{
|
|
Packer packer = Packer(false);
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
{ // unique value
|
|
mixin DefinePacker;
|
|
|
|
ubyte[] result = [Format.NIL, Format.TRUE, Format.FALSE];
|
|
|
|
packer.pack(null, true, false);
|
|
foreach (i, value; packer.stream.data)
|
|
assert(value == result[i]);
|
|
}
|
|
{ // uint *
|
|
static struct UTest { ubyte format; ulong value; }
|
|
|
|
enum : ulong { A = ubyte.max, B = ushort.max, C = uint.max, D = ulong.max }
|
|
|
|
static UTest[][] utests = [
|
|
[{Format.UINT8, A}],
|
|
[{Format.UINT8, A}, {Format.UINT16, B}],
|
|
[{Format.UINT8, A}, {Format.UINT16, B}, {Format.UINT32, C}],
|
|
[{Format.UINT8, A}, {Format.UINT16, B}, {Format.UINT32, C}, {Format.UINT64, D}],
|
|
];
|
|
|
|
foreach (I, T; TypeTuple!(ubyte, ushort, uint, ulong)) {
|
|
foreach (i, test; utests[I]) {
|
|
mixin DefinePacker;
|
|
|
|
packer.pack(cast(T)test.value);
|
|
assert(packer.stream.data[0] == test.format);
|
|
|
|
switch (i) {
|
|
case 0:
|
|
auto answer = take8from!(T.sizeof * 8)(test.value);
|
|
assert(memcmp(&packer.stream.data[1], &answer, ubyte.sizeof) == 0);
|
|
break;
|
|
case 1:
|
|
auto answer = convertEndianTo!16(test.value);
|
|
assert(memcmp(&packer.stream.data[1], &answer, ushort.sizeof) == 0);
|
|
break;
|
|
case 2:
|
|
auto answer = convertEndianTo!32(test.value);
|
|
assert(memcmp(&packer.stream.data[1], &answer, uint.sizeof) == 0);
|
|
break;
|
|
default:
|
|
auto answer = convertEndianTo!64(test.value);
|
|
assert(memcmp(&packer.stream.data[1], &answer, ulong.sizeof) == 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
{ // int *
|
|
static struct STest { ubyte format; long value; }
|
|
|
|
enum : long { A = byte.min, B = short.min, C = int.min, D = long.min }
|
|
|
|
static STest[][] stests = [
|
|
[{Format.INT8, A}],
|
|
[{Format.INT8, A}, {Format.INT16, B}],
|
|
[{Format.INT8, A}, {Format.INT16, B}, {Format.INT32, C}],
|
|
[{Format.INT8, A}, {Format.INT16, B}, {Format.INT32, C}, {Format.INT64, D}],
|
|
];
|
|
|
|
foreach (I, T; TypeTuple!(byte, short, int, long)) {
|
|
foreach (i, test; stests[I]) {
|
|
mixin DefinePacker;
|
|
|
|
packer.pack(cast(T)test.value);
|
|
assert(packer.stream.data[0] == test.format);
|
|
|
|
switch (i) {
|
|
case 0:
|
|
auto answer = take8from!(T.sizeof * 8)(test.value);
|
|
assert(memcmp(&packer.stream.data[1], &answer, byte.sizeof) == 0);
|
|
break;
|
|
case 1:
|
|
auto answer = convertEndianTo!16(test.value);
|
|
assert(memcmp(&packer.stream.data[1], &answer, short.sizeof) == 0);
|
|
break;
|
|
case 2:
|
|
auto answer = convertEndianTo!32(test.value);
|
|
assert(memcmp(&packer.stream.data[1], &answer, int.sizeof) == 0);
|
|
break;
|
|
default:
|
|
auto answer = convertEndianTo!64(test.value);
|
|
assert(memcmp(&packer.stream.data[1], &answer, long.sizeof) == 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
{ // fload, double
|
|
static if ((real.sizeof == double.sizeof) || !EnableReal)
|
|
{
|
|
alias TypeTuple!(float, double, double) FloatingTypes;
|
|
static struct FTest { ubyte format; double value; }
|
|
|
|
static FTest[] ftests = [
|
|
{Format.FLOAT, float.min_normal},
|
|
{Format.DOUBLE, double.max},
|
|
{Format.DOUBLE, double.max},
|
|
];
|
|
}
|
|
else
|
|
{
|
|
alias TypeTuple!(float, double, real) FloatingTypes;
|
|
static struct FTest { ubyte format; real value; }
|
|
|
|
static FTest[] ftests = [
|
|
{Format.FLOAT, float.min_normal},
|
|
{Format.DOUBLE, double.max},
|
|
{Format.REAL, real.max},
|
|
];
|
|
}
|
|
|
|
foreach (I, T; FloatingTypes) {
|
|
mixin DefinePacker;
|
|
|
|
packer.pack(cast(T)ftests[I].value);
|
|
assert(packer.stream.data[0] == ftests[I].format);
|
|
|
|
switch (I) {
|
|
case 0:
|
|
const answer = convertEndianTo!32(_f(cast(T)ftests[I].value).i);
|
|
assert(memcmp(&packer.stream.data[1], &answer, float.sizeof) == 0);
|
|
break;
|
|
case 1:
|
|
const answer = convertEndianTo!64(_d(cast(T)ftests[I].value).i);
|
|
assert(memcmp(&packer.stream.data[1], &answer, double.sizeof) == 0);
|
|
break;
|
|
default:
|
|
static if (EnableReal)
|
|
{
|
|
const t = _r(cast(T)ftests[I].value);
|
|
const f = convertEndianTo!64(t.fraction);
|
|
const e = convertEndianTo!16(t.exponent);
|
|
assert(memcmp(&packer.stream.data[1], &f, f.sizeof) == 0);
|
|
assert(memcmp(&packer.stream.data[1 + f.sizeof], &e, e.sizeof) == 0);
|
|
}
|
|
else
|
|
{
|
|
const answer = convertEndianTo!64(_d(cast(T)ftests[I].value).i);
|
|
assert(memcmp(&packer.stream.data[1], &answer, double.sizeof) == 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
{ // pointer
|
|
static struct PTest
|
|
{
|
|
ubyte format;
|
|
|
|
union
|
|
{
|
|
ulong* p0;
|
|
long* p1;
|
|
double* p2;
|
|
}
|
|
}
|
|
|
|
PTest[] ptests = [PTest(Format.UINT64), PTest(Format.INT64), PTest(Format.DOUBLE)];
|
|
|
|
ulong v0 = ulong.max;
|
|
long v1 = long.min;
|
|
double v2 = double.max;
|
|
|
|
foreach (I, Index; TypeTuple!("0", "1", "2")) {
|
|
mixin DefinePacker;
|
|
|
|
mixin("ptests[I].p" ~ Index ~ " = &v" ~ Index ~ ";");
|
|
|
|
packer.pack(mixin("ptests[I].p" ~ Index));
|
|
assert(packer.stream.data[0] == ptests[I].format);
|
|
|
|
switch (I) {
|
|
case 0:
|
|
auto answer = convertEndianTo!64(*ptests[I].p0);
|
|
assert(memcmp(&packer.stream.data[1], &answer, ulong.sizeof) == 0);
|
|
break;
|
|
case 1:
|
|
auto answer = convertEndianTo!64(*ptests[I].p1);
|
|
assert(memcmp(&packer.stream.data[1], &answer, long.sizeof) == 0);
|
|
break;
|
|
default:
|
|
const answer = convertEndianTo!64(_d(*ptests[I].p2).i);
|
|
assert(memcmp(&packer.stream.data[1], &answer, double.sizeof) == 0);
|
|
}
|
|
}
|
|
}
|
|
{ // enum
|
|
enum E : ubyte { A = ubyte.max }
|
|
|
|
mixin DefinePacker; E e = E.A;
|
|
|
|
packer.pack(e);
|
|
assert(packer.stream.data[0] == Format.UINT8);
|
|
|
|
auto answer = E.A;
|
|
assert(memcmp(&packer.stream.data[1], &answer, (OriginalType!E).sizeof) == 0);
|
|
}
|
|
{ // container
|
|
static struct CTest { ubyte format; size_t value; }
|
|
|
|
enum : ulong { A = 16 / 2, B = ushort.max, C = uint.max }
|
|
|
|
static CTest[][] ctests = [
|
|
[{Format.ARRAY | A, Format.ARRAY | A}, {Format.ARRAY16, B}, {Format.ARRAY32, C}],
|
|
[{Format.MAP | A, Format.MAP | A}, {Format.MAP16, B}, {Format.MAP32, C}],
|
|
];
|
|
|
|
foreach (I, Name; TypeTuple!("Array", "Map")) {
|
|
auto test = ctests[I];
|
|
|
|
foreach (i, T; TypeTuple!(ubyte, ushort, uint)) {
|
|
mixin DefinePacker;
|
|
mixin("packer.begin" ~ Name ~ "(i ? test[i].value : A);");
|
|
|
|
assert(packer.stream.data[0] == test[i].format);
|
|
|
|
switch (i) {
|
|
case 0:
|
|
auto answer = take8from(test[i].value);
|
|
assert(memcmp(&packer.stream.data[0], &answer, ubyte.sizeof) == 0);
|
|
break;
|
|
case 1:
|
|
auto answer = convertEndianTo!16(test[i].value);
|
|
assert(memcmp(&packer.stream.data[1], &answer, ushort.sizeof) == 0);
|
|
break;
|
|
default:
|
|
auto answer = convertEndianTo!32(test[i].value);
|
|
assert(memcmp(&packer.stream.data[1], &answer, uint.sizeof) == 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
{ // user defined
|
|
{
|
|
static struct S
|
|
{
|
|
uint num = uint.max;
|
|
|
|
void toMsgpack(P)(ref P p) const { p.packArray(num); }
|
|
}
|
|
|
|
mixin DefinePacker; S test;
|
|
|
|
packer.pack(test);
|
|
|
|
assert(packer.stream.data[0] == (Format.ARRAY | 1));
|
|
assert(packer.stream.data[1] == Format.UINT32);
|
|
assert(memcmp(&packer.stream.data[2], &test.num, uint.sizeof) == 0);
|
|
}
|
|
{
|
|
mixin DefinePacker; auto test = tuple(true, false, uint.max);
|
|
|
|
packer.pack(test);
|
|
|
|
assert(packer.stream.data[0] == (Format.ARRAY | 3));
|
|
assert(packer.stream.data[1] == Format.TRUE);
|
|
assert(packer.stream.data[2] == Format.FALSE);
|
|
assert(packer.stream.data[3] == Format.UINT32);
|
|
assert(memcmp(&packer.stream.data[4], &test.field[2], uint.sizeof) == 0);
|
|
}
|
|
{
|
|
static class C
|
|
{
|
|
uint num;
|
|
|
|
this(uint n) { num = n; }
|
|
|
|
void toMsgpack(P)(ref P p) const { p.packArray(num); }
|
|
}
|
|
|
|
mixin DefinePacker; C test = new C(ushort.max);
|
|
|
|
packer.pack(test);
|
|
|
|
assert(packer.stream.data[0] == (Format.ARRAY | 1));
|
|
assert(packer.stream.data[1] == Format.UINT16);
|
|
assert(memcmp(&packer.stream.data[2], &test.num, ushort.sizeof) == 0);
|
|
}
|
|
}
|
|
{ // simple struct and class
|
|
{
|
|
static struct Simple
|
|
{
|
|
uint num = uint.max;
|
|
}
|
|
|
|
static struct SimpleWithNonPacked1
|
|
{
|
|
uint num = uint.max;
|
|
@nonPacked string str = "ignored";
|
|
}
|
|
|
|
static struct SimpleWithNonPacked2
|
|
{
|
|
@nonPacked string str = "ignored";
|
|
uint num = uint.max;
|
|
}
|
|
|
|
static struct SimpleWithSkippedTypes
|
|
{
|
|
int function(int) fn;
|
|
int delegate(int) dg;
|
|
uint num = uint.max;
|
|
}
|
|
|
|
foreach (Type; TypeTuple!(Simple, SimpleWithNonPacked1, SimpleWithNonPacked2, SimpleWithSkippedTypes)) {
|
|
mixin DefinePacker;
|
|
|
|
Type test;
|
|
packer.pack(test);
|
|
|
|
assert(packer.stream.data[0] == (Format.ARRAY | 1));
|
|
assert(packer.stream.data[1] == Format.UINT32);
|
|
assert(memcmp(&packer.stream.data[2], &test.num, uint.sizeof) == 0);
|
|
}
|
|
}
|
|
|
|
static class SimpleA
|
|
{
|
|
bool flag = true;
|
|
}
|
|
|
|
static class SimpleB : SimpleA
|
|
{
|
|
ubyte type = 100;
|
|
}
|
|
|
|
static class SimpleC : SimpleB
|
|
{
|
|
uint num = uint.max;
|
|
}
|
|
|
|
static class SimpleCWithNonPacked1 : SimpleB
|
|
{
|
|
uint num = uint.max;
|
|
@nonPacked string str = "ignored";
|
|
}
|
|
|
|
static class SimpleCWithNonPacked2 : SimpleB
|
|
{
|
|
@nonPacked string str = "ignored";
|
|
uint num = uint.max;
|
|
}
|
|
|
|
static class SimpleCWithSkippedTypes : SimpleB
|
|
{
|
|
uint num = uint.max;
|
|
int function(int) fn;
|
|
int delegate(int) dg;
|
|
}
|
|
|
|
{ // from derived class
|
|
foreach (Type; TypeTuple!(SimpleC, SimpleCWithNonPacked1, SimpleCWithNonPacked2, SimpleCWithSkippedTypes)) {
|
|
mixin DefinePacker;
|
|
|
|
Type test = new Type();
|
|
packer.pack(test);
|
|
|
|
assert(packer.stream.data[0] == (Format.ARRAY | 3));
|
|
assert(packer.stream.data[1] == Format.TRUE);
|
|
assert(packer.stream.data[2] == 100);
|
|
assert(packer.stream.data[3] == Format.UINT32);
|
|
assert(memcmp(&packer.stream.data[4], &test.num, uint.sizeof) == 0);
|
|
}
|
|
}
|
|
{ // from base class
|
|
mixin DefinePacker; SimpleB test = new SimpleC();
|
|
|
|
try {
|
|
packer.pack(test);
|
|
assert(false);
|
|
} catch (Exception e) { }
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// deserializing routines
|
|
|
|
|
|
/**
|
|
* $(D UnpackException) is thrown on deserialization failure
|
|
*/
|
|
class UnpackException : MessagePackException
|
|
{
|
|
this(string message)
|
|
{
|
|
super(message);
|
|
}
|
|
}
|
|
|
|
|
|
version (D_Ddoc)
|
|
{
|
|
/**
|
|
* Internal buffer and related operations for Unpacker
|
|
*
|
|
* Following Unpackers mixin this template. So, Unpacker can use following methods.
|
|
*
|
|
* -----
|
|
* //buffer image:
|
|
* +-------------------------------------------+
|
|
* | [object] | [obj | unparsed... | unused... |
|
|
* +-------------------------------------------+
|
|
* ^ offset
|
|
* ^ current
|
|
* ^ used
|
|
* ^ buffer.length
|
|
* -----
|
|
*
|
|
* This mixin template is a private.
|
|
*/
|
|
mixin template InternalBuffer()
|
|
{
|
|
private:
|
|
ubyte[] buffer_; // internal buffer
|
|
size_t used_; // index that buffer cosumed
|
|
size_t offset_; // index that buffer parsed
|
|
size_t parsed_; // total size of parsed message
|
|
bool hasRaw_; // indicates whether Raw object has been deserialized
|
|
|
|
|
|
public:
|
|
/**
|
|
* Forwards to internal buffer.
|
|
*
|
|
* Returns:
|
|
* the reference of internal buffer.
|
|
*/
|
|
@property @safe
|
|
nothrow ubyte[] buffer();
|
|
|
|
|
|
/**
|
|
* Fills internal buffer with $(D_PARAM target).
|
|
*
|
|
* Params:
|
|
* target = new serialized buffer to deserialize.
|
|
*/
|
|
@safe void feed(in ubyte[] target);
|
|
|
|
|
|
/**
|
|
* Consumes buffer. This method is helper for buffer property.
|
|
* You must use this method if you write bytes to buffer directly.
|
|
*
|
|
* Params:
|
|
* size = the number of consuming.
|
|
*/
|
|
@safe
|
|
nothrow void bufferConsumed(in size_t size);
|
|
|
|
|
|
/**
|
|
* Removes unparsed buffer.
|
|
*/
|
|
@safe
|
|
nothrow void removeUnparsed();
|
|
|
|
|
|
/**
|
|
* Returns:
|
|
* the total size including unparsed buffer size.
|
|
*/
|
|
@property @safe
|
|
nothrow size_t size() const;
|
|
|
|
|
|
/**
|
|
* Returns:
|
|
* the parsed size of buffer.
|
|
*/
|
|
@property @safe
|
|
nothrow size_t parsedSize() const;
|
|
|
|
|
|
/**
|
|
* Returns:
|
|
* the unparsed size of buffer.
|
|
*/
|
|
@property @safe
|
|
nothrow size_t unparsedSize() const;
|
|
|
|
|
|
private:
|
|
@safe
|
|
void initializeBuffer(in ubyte[] target, in size_t bufferSize = 8192);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
private mixin template InternalBuffer()
|
|
{
|
|
private:
|
|
ubyte[] buffer_; // internal buffer
|
|
size_t used_; // index that buffer cosumed
|
|
size_t offset_; // index that buffer parsed
|
|
size_t parsed_; // total size of parsed message
|
|
bool hasRaw_; // indicates whether Raw object has been deserialized
|
|
|
|
|
|
public:
|
|
@property @safe
|
|
nothrow ubyte[] buffer()
|
|
{
|
|
return buffer_;
|
|
}
|
|
|
|
|
|
@safe
|
|
void feed(in ubyte[] target)
|
|
in
|
|
{
|
|
assert(target.length);
|
|
}
|
|
body
|
|
{
|
|
/*
|
|
* Expands internal buffer.
|
|
*
|
|
* Params:
|
|
* size = new buffer size to append.
|
|
*/
|
|
void expandBuffer(in size_t size)
|
|
{
|
|
// rewinds buffer(completed deserialization)
|
|
if (used_ == offset_ && !hasRaw_) {
|
|
used_ = offset_ = 0;
|
|
|
|
if (buffer_.length < size)
|
|
buffer_.length = size;
|
|
|
|
return;
|
|
}
|
|
|
|
// deserializing state is mid-flow(buffer has non-parsed data yet)
|
|
auto unparsed = buffer_[offset_..used_];
|
|
auto restSize = buffer_.length - used_ + offset_;
|
|
auto newSize = size > restSize ? unparsedSize + size : buffer_.length;
|
|
|
|
if (hasRaw_) {
|
|
hasRaw_ = false;
|
|
buffer_ = new ubyte[](newSize);
|
|
} else {
|
|
buffer_.length = newSize;
|
|
|
|
// avoids overlapping copy
|
|
auto area = buffer_[0..unparsedSize];
|
|
unparsed = area.overlap(unparsed) ? unparsed.dup : unparsed;
|
|
}
|
|
|
|
buffer_[0..unparsedSize] = unparsed[];
|
|
used_ = unparsedSize;
|
|
offset_ = 0;
|
|
}
|
|
|
|
const size = target.length;
|
|
|
|
// lacks current buffer?
|
|
if (buffer_.length - used_ < size)
|
|
expandBuffer(size);
|
|
|
|
buffer_[used_..used_ + size] = target[];
|
|
used_ += size;
|
|
}
|
|
|
|
|
|
@safe
|
|
nothrow void bufferConsumed(in size_t size)
|
|
{
|
|
if (used_ + size > buffer_.length)
|
|
used_ = buffer_.length;
|
|
else
|
|
used_ += size;
|
|
}
|
|
|
|
|
|
@safe
|
|
nothrow void removeUnparsed()
|
|
{
|
|
used_ = offset_;
|
|
}
|
|
|
|
|
|
@property @safe
|
|
nothrow size_t size() const
|
|
{
|
|
return parsed_ - offset_ + used_;
|
|
}
|
|
|
|
|
|
@property @safe
|
|
nothrow size_t parsedSize() const
|
|
{
|
|
return parsed_;
|
|
}
|
|
|
|
|
|
@property @safe
|
|
nothrow size_t unparsedSize() const
|
|
{
|
|
return used_ - offset_;
|
|
}
|
|
|
|
|
|
private:
|
|
@safe
|
|
nothrow void initializeBuffer(in ubyte[] target, in size_t bufferSize = 8192)
|
|
{
|
|
const size = target.length;
|
|
|
|
buffer_ = new ubyte[](size > bufferSize ? size : bufferSize);
|
|
used_ = size;
|
|
buffer_[0..size] = target[];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* This $(D Unpacker) is a $(D MessagePack) direct-conversion deserializer
|
|
*
|
|
* This implementation is suitable for fixed data.
|
|
*
|
|
* Example:
|
|
* -----
|
|
* // serializedData is [10, 0.1, false]
|
|
* auto unpacker = Unpacker(serializedData);
|
|
*
|
|
* uint n;
|
|
* double d;
|
|
* bool b;
|
|
*
|
|
* unpacker.unpackArray(n, d, b);
|
|
*
|
|
* // using Tuple
|
|
* Tuple!(uint, double, bool) record;
|
|
* unpacker.unpack(record); // record is [10, 0.1, false]
|
|
* -----
|
|
*
|
|
* NOTE:
|
|
* Unpacker becomes template struct if Phobos supports truly IO module.
|
|
*/
|
|
struct Unpacker
|
|
{
|
|
private:
|
|
static @system
|
|
{
|
|
alias void delegate(ref Unpacker, void*) UnpackHandler;
|
|
UnpackHandler[TypeInfo] unpackHandlers;
|
|
|
|
public void registerHandler(T, alias Handler)()
|
|
{
|
|
unpackHandlers[typeid(T)] = delegate(ref Unpacker unpacker, void* obj) {
|
|
Handler(unpacker, *cast(T*)obj);
|
|
};
|
|
}
|
|
}
|
|
|
|
enum Offset = 1;
|
|
|
|
mixin InternalBuffer;
|
|
|
|
bool withFieldName_;
|
|
|
|
|
|
public:
|
|
/**
|
|
* Constructs a $(D Unpacker).
|
|
*
|
|
* Params:
|
|
* target = byte buffer to deserialize
|
|
* bufferSize = size limit of buffer size
|
|
*/
|
|
this(in ubyte[] target, in size_t bufferSize = 8192, bool withFieldName = false)
|
|
{
|
|
initializeBuffer(target, bufferSize);
|
|
withFieldName_ = withFieldName;
|
|
}
|
|
|
|
|
|
/**
|
|
* Clears states for next deserialization.
|
|
*/
|
|
@safe
|
|
nothrow void clear()
|
|
{
|
|
parsed_ = 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Deserializes $(D_PARAM T) object and assigns to $(D_PARAM value).
|
|
*
|
|
* If the argument is pointer, dereferences pointer and assigns deserialized value.
|
|
* -----
|
|
* int* a;
|
|
* unpacker.unpack(a) // enforce throws Exception because a is null or
|
|
* // no throw if deserialized value is nil
|
|
*
|
|
* int b; a = &b;
|
|
* unpacker.unpack(b) // b is deserialized value or
|
|
* // assigns null if deserialized value is nil
|
|
* -----
|
|
*
|
|
* Params:
|
|
* value = the reference of value to assign.
|
|
*
|
|
* Returns:
|
|
* self, i.e. for method chaining.
|
|
*
|
|
* Throws:
|
|
* UnpackException when doesn't read from buffer or precision loss occurs and
|
|
* MessagePackException when $(D_PARAM T) type doesn't match serialized type.
|
|
*/
|
|
ref Unpacker unpack(T)(ref T value) if (is(Unqual!T == bool))
|
|
{
|
|
canRead(Offset, 0);
|
|
const header = read();
|
|
|
|
switch (header) {
|
|
case Format.TRUE:
|
|
value = true;
|
|
break;
|
|
case Format.FALSE:
|
|
value = false;
|
|
break;
|
|
default:
|
|
rollback();
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref Unpacker unpack(T)(ref T value) if (isUnsigned!T && !is(Unqual!T == enum))
|
|
{
|
|
canRead(Offset, 0);
|
|
const header = read();
|
|
|
|
if (0x00 <= header && header <= 0x7f) {
|
|
value = header;
|
|
} else {
|
|
switch (header) {
|
|
case Format.UINT8:
|
|
canRead(ubyte.sizeof);
|
|
value = read();
|
|
break;
|
|
case Format.UINT16:
|
|
canRead(ushort.sizeof);
|
|
auto us = load16To!ushort(read(ushort.sizeof));
|
|
if (us > T.max)
|
|
rollback(ushort.sizeof);
|
|
value = cast(T)us;
|
|
break;
|
|
case Format.UINT32:
|
|
canRead(uint.sizeof);
|
|
auto ui = load32To!uint(read(uint.sizeof));
|
|
if (ui > T.max)
|
|
rollback(uint.sizeof);
|
|
value = cast(T)ui;
|
|
break;
|
|
case Format.UINT64:
|
|
canRead(ulong.sizeof);
|
|
auto ul = load64To!ulong(read(ulong.sizeof));
|
|
if (ul > T.max)
|
|
rollback(ulong.sizeof);
|
|
value = cast(T)ul;
|
|
break;
|
|
default:
|
|
rollback();
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref Unpacker unpack(T)(ref T value) if (isSigned!T && isIntegral!T && !is(Unqual!T == enum))
|
|
{
|
|
canRead(Offset, 0);
|
|
const header = read();
|
|
|
|
if (0x00 <= header && header <= 0x7f) {
|
|
value = cast(T)header;
|
|
} else if (0xe0 <= header && header <= 0xff) {
|
|
value = -(cast(T)-header);
|
|
} else {
|
|
switch (header) {
|
|
case Format.UINT8:
|
|
canRead(ubyte.sizeof);
|
|
auto ub = read();
|
|
if (ub > T.max)
|
|
rollback(ubyte.sizeof);
|
|
value = cast(T)ub;
|
|
break;
|
|
case Format.UINT16:
|
|
canRead(ushort.sizeof);
|
|
auto us = load16To!ushort(read(ushort.sizeof));
|
|
if (us > T.max)
|
|
rollback(ushort.sizeof);
|
|
value = cast(T)us;
|
|
break;
|
|
case Format.UINT32:
|
|
canRead(uint.sizeof);
|
|
auto ui = load32To!uint(read(uint.sizeof));
|
|
if (ui > T.max)
|
|
rollback(uint.sizeof);
|
|
value = cast(T)ui;
|
|
break;
|
|
case Format.UINT64:
|
|
canRead(ulong.sizeof);
|
|
auto ul = load64To!ulong(read(ulong.sizeof));
|
|
if (ul > T.max)
|
|
rollback(ulong.sizeof);
|
|
value = cast(T)ul;
|
|
break;
|
|
case Format.INT8:
|
|
canRead(byte.sizeof);
|
|
value = cast(byte)read();
|
|
break;
|
|
case Format.INT16:
|
|
canRead(short.sizeof);
|
|
auto s = load16To!short(read(short.sizeof));
|
|
if (s < T.min || T.max < s)
|
|
rollback(short.sizeof);
|
|
value = cast(T)s;
|
|
break;
|
|
case Format.INT32:
|
|
canRead(int.sizeof);
|
|
auto i = load32To!int(read(int.sizeof));
|
|
if (i < T.min || T.max < i)
|
|
rollback(int.sizeof);
|
|
value = cast(T)i;
|
|
break;
|
|
case Format.INT64:
|
|
canRead(long.sizeof);
|
|
auto l = load64To!long(read(long.sizeof));
|
|
if (l < T.min || T.max < l)
|
|
rollback(long.sizeof);
|
|
value = cast(T)l;
|
|
break;
|
|
default:
|
|
rollback();
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref Unpacker unpack(T)(ref T value) if (isFloatingPoint!T && !is(Unqual!T == enum))
|
|
{
|
|
canRead(Offset, 0);
|
|
const header = read();
|
|
|
|
switch (header) {
|
|
case Format.FLOAT:
|
|
_f temp;
|
|
|
|
canRead(uint.sizeof);
|
|
temp.i = load32To!uint(read(uint.sizeof));
|
|
value = temp.f;
|
|
break;
|
|
case Format.DOUBLE:
|
|
// check precision loss
|
|
static if (is(Unqual!T == float))
|
|
rollback();
|
|
|
|
_d temp;
|
|
|
|
canRead(ulong.sizeof);
|
|
temp.i = load64To!ulong(read(ulong.sizeof));
|
|
value = temp.f;
|
|
break;
|
|
case Format.REAL:
|
|
static if (!EnableReal)
|
|
{
|
|
rollback();
|
|
}
|
|
else
|
|
{
|
|
// check precision loss
|
|
static if (is(Unqual!T == float) || is(Unqual!T == double))
|
|
rollback();
|
|
|
|
canRead(RealSize);
|
|
|
|
version (NonX86)
|
|
{
|
|
CustomFloat!80 temp;
|
|
|
|
const frac = load64To!ulong (read(ulong.sizeof));
|
|
const exp = load16To!ushort(read(ushort.sizeof));
|
|
|
|
temp.significand = frac;
|
|
temp.exponent = exp & 0x7fff;
|
|
temp.sign = exp & 0x8000 ? true : false;
|
|
|
|
// NOTE: temp.get!real is inf on non-x86 when deserialized value is larger than double.max.
|
|
value = temp.get!real;
|
|
}
|
|
else
|
|
{
|
|
_r temp;
|
|
|
|
temp.fraction = load64To!(typeof(temp.fraction))(read(temp.fraction.sizeof));
|
|
temp.exponent = load16To!(typeof(temp.exponent))(read(temp.exponent.sizeof));
|
|
|
|
value = temp.f;
|
|
}
|
|
}
|
|
|
|
break;
|
|
default:
|
|
rollback();
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref Unpacker unpack(T)(ref T value) if (is(Unqual!T == enum))
|
|
{
|
|
OriginalType!T temp;
|
|
|
|
unpack(temp);
|
|
|
|
value = cast(T)temp;
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref Unpacker unpack(T)(T value) if (isPointer!T)
|
|
{
|
|
static if (is(Unqual!T == void*)) {
|
|
enforce(value !is null, "Can't deserialize void type");
|
|
unpackNil(value);
|
|
} else {
|
|
if (checkNil())
|
|
unpackNil(value);
|
|
else
|
|
enforce(value !is null, T.stringof ~ " is null pointer");
|
|
|
|
unpack(mixin(AsteriskOf!T ~ "value"));
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref Unpacker unpack(Types...)(ref Types objects) if (Types.length > 1)
|
|
{
|
|
foreach (i, T; Types)
|
|
unpack!(T)(objects[i]);
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/**
|
|
* Deserializes $(D_PARAM T) object and assigns to $(D_PARAM array).
|
|
*
|
|
* This is convenient method for array deserialization.
|
|
* Rollback will be completely successful if you deserialize raw type((u)byte[] or string types).
|
|
* But, Rollback will be one element(e.g. int) if you deserialize other types(e.g. int[], int[int])
|
|
*
|
|
* No assign if the length of deserialized object is 0.
|
|
*
|
|
* In a static array, this method checks the length. Do rollback and throw exception
|
|
* if length of $(D_PARAM array) is different from length of deserialized object.
|
|
*
|
|
* Params:
|
|
* array = the reference of array to assign.
|
|
*
|
|
* Returns:
|
|
* self, i.e. for method chaining.
|
|
*
|
|
* Throws:
|
|
* UnpackException when doesn't read from buffer or precision loss occurs and
|
|
* MessagePackException when $(D_PARAM T) type doesn't match serialized type.
|
|
*/
|
|
ref Unpacker unpack(T)(ref T array) if (isArray!T && !is(Unqual!T == enum))
|
|
{
|
|
alias typeof(T.init[0]) U;
|
|
|
|
/*
|
|
* Deserializes type-information of raw type.
|
|
*/
|
|
@safe
|
|
size_t beginRaw()
|
|
{
|
|
canRead(Offset, 0);
|
|
const header = read();
|
|
size_t length;
|
|
|
|
if (0xa0 <= header && header <= 0xbf) {
|
|
length = header & 0x1f;
|
|
} else {
|
|
switch (header) {
|
|
case Format.BIN8, Format.STR8:
|
|
canRead(ubyte.sizeof);
|
|
length = read();
|
|
break;
|
|
case Format.BIN16, Format.RAW16:
|
|
canRead(ushort.sizeof);
|
|
length = load16To!size_t(read(ushort.sizeof));
|
|
break;
|
|
case Format.BIN32, Format.RAW32:
|
|
canRead(uint.sizeof);
|
|
length = load32To!size_t(read(uint.sizeof));
|
|
break;
|
|
case Format.NIL:
|
|
break;
|
|
default:
|
|
rollback();
|
|
}
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
|
|
if (checkNil()) {
|
|
static if (isStaticArray!T) {
|
|
onInvalidType();
|
|
} else {
|
|
return unpackNil(array);
|
|
}
|
|
}
|
|
|
|
// Raw bytes
|
|
static if (isByte!U || isSomeChar!U) {
|
|
auto length = beginRaw();
|
|
auto offset = calculateSize!(true)(length);
|
|
if (length == 0)
|
|
return this;
|
|
|
|
static if (isStaticArray!T) {
|
|
if (length != array.length)
|
|
rollback(offset);
|
|
}
|
|
|
|
canRead(length, offset + Offset);
|
|
static if (isStaticArray!T) {
|
|
array[] = (cast(U[])read(length))[0 .. T.length];
|
|
} else {
|
|
array = cast(T)read(length);
|
|
}
|
|
|
|
static if (isDynamicArray!T)
|
|
hasRaw_ = true;
|
|
} else {
|
|
auto length = beginArray();
|
|
if (length == 0)
|
|
return this;
|
|
|
|
static if (isStaticArray!T) {
|
|
if (length != array.length)
|
|
rollback(calculateSize(length));
|
|
} else {
|
|
array.length = length;
|
|
}
|
|
|
|
foreach (i; 0..length)
|
|
unpack(array[i]);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref Unpacker unpack(T)(ref T array) if (isAssociativeArray!T)
|
|
{
|
|
alias typeof(T.init.keys[0]) K;
|
|
alias typeof(T.init.values[0]) V;
|
|
|
|
if (checkNil())
|
|
return unpackNil(array);
|
|
|
|
auto length = beginMap();
|
|
if (length == 0)
|
|
return this;
|
|
|
|
foreach (i; 0..length) {
|
|
K k; unpack(k);
|
|
V v; unpack(v);
|
|
array[k] = v;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/**
|
|
* Deserializes $(D_PARAM T) object and assigns to $(D_PARAM object).
|
|
*
|
|
* Calling $(D fromMsgpack) if $(D_KEYWORD class) and $(D_KEYWORD struct) implement $(D fromMsgpack) method. $(D fromMsgpack) signature is:
|
|
* -----
|
|
* void fromMsgpack(ref Unpacker unpacker)
|
|
* -----
|
|
* Assumes $(D std.typecons.Tuple) or simple struct if $(D_KEYWORD struct) doesn't implement $(D fromMsgpack).
|
|
* Checks length if $(D_PARAM T) is a $(D std.typecons.Tuple) or simple struct.
|
|
*
|
|
* Params:
|
|
* object = the reference of object to assign.
|
|
* args = the arguments to class constructor(class only).
|
|
* This is used at new statement if $(D_PARAM object) is $(D_KEYWORD null).
|
|
*
|
|
* Returns:
|
|
* self, i.e. for method chaining.
|
|
*/
|
|
ref Unpacker unpack(T, Args...)(ref T object, auto ref Args args) if (is(Unqual!T == class))
|
|
{
|
|
if (checkNil())
|
|
return unpackNil(object);
|
|
|
|
if (object is null) {
|
|
//static if (is(typeof(new T(args))))
|
|
static if (__traits(compiles, { new T(args); }))
|
|
object = new T(args);
|
|
else
|
|
throw new MessagePackException("Don't know how to construct class type '" ~ Unqual!T.stringof ~ "' with argument types '" ~ Args.stringof ~ "'.");
|
|
}
|
|
|
|
static if (hasMember!(T, "fromMsgpack"))
|
|
{
|
|
static if (__traits(compiles, { T t; t.fromMsgpack(this, withFieldName_); })) {
|
|
object.fromMsgpack(this, withFieldName_);
|
|
} else static if (__traits(compiles, { T t; t.fromMsgpack(this); })) { // backward compatible
|
|
object.fromMsgpack(this);
|
|
} else {
|
|
static assert(0, "Failed to invoke 'fromMsgpack' on type '" ~ Unqual!T.stringof ~ "'");
|
|
}
|
|
} else {
|
|
if (auto handler = object.classinfo in unpackHandlers) {
|
|
(*handler)(this, cast(void*)&object);
|
|
return this;
|
|
}
|
|
if (T.classinfo !is object.classinfo) {
|
|
throw new MessagePackException("Can't unpack derived class through reference to base class.");
|
|
}
|
|
|
|
alias SerializingClasses!(T) Classes;
|
|
|
|
size_t length = withFieldName_ ? beginMap() : beginArray();
|
|
if (length == 0)
|
|
return this;
|
|
|
|
if (length != SerializingMemberNumbers!(Classes))
|
|
rollback(calculateSize(length));
|
|
|
|
if (withFieldName_) {
|
|
foreach (_; 0..length) {
|
|
string fieldName;
|
|
unpack(fieldName);
|
|
|
|
foreach (Class; Classes) {
|
|
Class obj = cast(Class)object;
|
|
|
|
foreach (i, member; obj.tupleof) {
|
|
static if (isPackedField!(Class.tupleof[i]))
|
|
{
|
|
if (fieldName == getFieldName!(Class, i)) {
|
|
unpack(obj.tupleof[i]);
|
|
goto endLoop;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert(false, "Invalid field name: '" ~ fieldName~"' ");
|
|
|
|
endLoop:
|
|
continue;
|
|
}
|
|
} else {
|
|
foreach (Class; Classes) {
|
|
Class obj = cast(Class)object;
|
|
|
|
foreach (i, member; obj.tupleof) {
|
|
static if (isPackedField!(Class.tupleof[i]))
|
|
unpack(obj.tupleof[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref Unpacker unpack(T)(ref T object) if (is(Unqual!T == struct))
|
|
{
|
|
static if (hasMember!(T, "fromMsgpack"))
|
|
{
|
|
static if (__traits(compiles, { T t; t.fromMsgpack(this); })) {
|
|
object.fromMsgpack(this);
|
|
} else {
|
|
static assert(0, "Failed to invoke 'fromMsgpack' on type '" ~ Unqual!T.stringof ~ "'");
|
|
}
|
|
} else {
|
|
if (auto handler = typeid(T) in unpackHandlers) {
|
|
(*handler)(this, cast(void*)&object);
|
|
return this;
|
|
}
|
|
|
|
auto length = beginArray();
|
|
if (length == 0)
|
|
return this;
|
|
|
|
static if (isTuple!T) {
|
|
if (length != T.Types.length)
|
|
rollback(calculateSize(length));
|
|
|
|
foreach (i, Type; T.Types)
|
|
unpack(object.field[i]);
|
|
} else { // simple struct
|
|
//if (length != object.tupleof.length)
|
|
if (length != SerializingMemberNumbers!(T))
|
|
rollback(calculateSize(length));
|
|
|
|
foreach (i, member; object.tupleof) {
|
|
static if (isPackedField!(T.tupleof[i]))
|
|
unpack(object.tupleof[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/**
|
|
* Deserializes the container object and assigns to each argument.
|
|
*
|
|
* These methods check the length. Do rollback if
|
|
* the length of arguments is different from length of deserialized object.
|
|
*
|
|
* In unpackMap, the number of arguments must be even.
|
|
*
|
|
* Params:
|
|
* objects = the references of object to assign.
|
|
*
|
|
* Returns:
|
|
* self, i.e. for method chaining.
|
|
*/
|
|
ref Unpacker unpackArray(Types...)(ref Types objects)
|
|
{
|
|
auto length = beginArray();
|
|
if (length != Types.length)
|
|
rollback(calculateSize(length));
|
|
|
|
foreach (i, T; Types)
|
|
unpack(objects[i]);
|
|
// unpack(objects); // slow :(
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// ditto
|
|
ref Unpacker unpackMap(Types...)(ref Types objects)
|
|
{
|
|
static assert(Types.length % 2 == 0, "The number of arguments must be even");
|
|
|
|
auto length = beginMap();
|
|
if (length != Types.length / 2)
|
|
rollback(calculateSize(length));
|
|
|
|
foreach (i, T; Types)
|
|
unpack( |