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

// 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(