Module msgpack_ll

A low-level pure @nogc, nothrow, @safe and betterC MessagePack implementation.

Note

As this is a low-level implementation certain error checking a some handling of the MessagePack data format has to be done by the API user. The following conditions need to be ensured by the user:

  • When calling parseType the compile time type must match the actual data type or incorrect results will be returned. Use getType to verify the type before calling parseType.
  • The fix types have certain maximum and minimum values. These conditions need to be ensured when calling formatType!T:
    • MsgpackType.posFixInt: Value must satisfy value < 128
    • MsgpackType.negFixInt: Value must satisfy -33 < value < 0
    • MsgpackType.fixStr: Length must satisfy length < 32
    • MsgpackType.fixArray: Length must satisfy length < 16
    • MsgpackType.fixMap: Length must satisfy length < 16
    • All ext types: extType must satisfy extType < 128
    Other size restrictions are automatically enforced by proper typing.
  • The debug=DebugMsgpackLL debug version can be used to enable debug checks for these problems.
  • Proper formatting and parsing of complex types (maps, arrays, ext types) needs help from the API user and must be done according to the MessagePack specification. For example to parse an array16 of int8:

    ubyte[] data = ...;
    byte[] result;
    // First read array length
    enforce(getType(data[0]) == MsgpackType.array16);
    auto length = parseType!(MsgpackType.array16)(data[0..DataSize!(MsgpackType.array16)]);
    data = data[DataSize!(MsgpackType.array16) .. $];
    // Then read array values
    for(size_t i = 0; i < length; i++)
    {
        enforce(getType(data[0]) == MsgpackType.int8);
        result ~= parseType!(MsgpackType.int8)(data[0..DataSize!(MsgpackType.int8)]);
        data = data[DataSize!(MsgpackType.int8) .. $];
    }

  • Requires only std.bitmanip for bigEndianToNative and nativeToBigEndian as external dependency.

    TODO

    Could try to avoid that dependency. This is only a compile time dependency anyway though, as these functions are templates and get inlined into this module.

    Example

    Most types are handled like this:

    ubyte[128] buffer;
    enum type = MsgpackType.uint8;
    
    // Serialization
    formatType!(type)(42, buffer[0 .. DataSize!type]);
    
    // Now deserialize
    // Get the type at runtime
    assert(getType(buffer[0]) == type);
    // and deserialize specifying the type at compile time
    const result = parseType!type(buffer[0 .. DataSize!type]);
    assert(result == 42);
    

    Example

    Values for nil, true8 and false8 are ignored and can be skipped:

    ubyte[128] buffer;
    enum type = MsgpackType.true8;
    
    // Serialization
    formatType!(type)(buffer[0 .. DataSize!type]);
    
    // Now deserialize
    // Get the type at runtime
    assert(getType(buffer[0]) == type);
    // and deserialize specifying the type at compile time
    const result = parseType!type(buffer[0 .. DataSize!type]);
    assert(result == true);
    

    Example

    The fixExt types accept an additional extType parameter and data:

    ubyte[128] buffer;
    ubyte[1] value = [1];
    enum type = MsgpackType.fixExt1;
    
    // Serialization
    formatType!(type)(42, value, buffer[0 .. DataSize!type]);
    
    // Now deserialize
    // Get the type at runtime
    assert(getType(buffer[0]) == type);
    const result = parseType!type(buffer[0 .. DataSize!type]);
    // and deserialize specifying the type at compile time
    assert(result[0] == 42);
    assert(result[1 .. $] == value);
    

    Example

    The ext types accept an additional extType parameter and data length.

    ubyte[128] buffer;
    enum type = MsgpackType.ext8;
    
    // Serialization
    formatType!(type)(10, 42, buffer[0 .. DataSize!type]);
    
    // Now deserialize
    // Get the type at runtime
    assert(getType(buffer[0]) == type);
    // and deserialize specifying the type at compile time
    const result = parseType!type(buffer[0 .. DataSize!type]);
    assert(result.type == 42);
    assert(result.length == 10);
    

    Example

    Often you'll want to decode multiple possible types:

    ulong decodeSomeUint(ubyte[] data)
    {
        switch (data[0].getType())
        {
        case MsgpackType.posFixInt:
            return parseType!(MsgpackType.posFixInt)(
                data[0 .. DataSize!(MsgpackType.posFixInt)]);
        case MsgpackType.uint8:
            return parseType!(MsgpackType.uint8)(
                data[0 .. DataSize!(MsgpackType.uint8)]);
        case MsgpackType.uint16:
            return parseType!(MsgpackType.uint16)(
                data[0 .. DataSize!(MsgpackType.uint16)]);
        case MsgpackType.uint32:
            return parseType!(MsgpackType.uint32)(
                data[0 .. DataSize!(MsgpackType.uint32)]);
        case MsgpackType.uint64:
            return parseType!(MsgpackType.uint64)(
                data[0 .. DataSize!(MsgpackType.uint64)]);
        default:
            throw new Exception("Expected integer type");
        }
    }
    

    Functions

    NameDescription
    formatType(value, data)Serialize a value to a certain type.
    getDataSize(type)Get serialized data size at runtime. DataSize!() should be preferred if the type is known at compile time.
    getType(value)Look at the first byte of an object to determine the type.
    parseType(data)Parses the MessagePack object with specified type.

    Structs

    NameDescription
    ExtTypeSerialization information about an ext type.

    Enums

    NameDescription
    MsgpackTypeEnum of MessagePack types.

    Manifest constants

    NameTypeDescription
    DataSizeGet serialized data size at compile time.