Support basic unpacking

master
Marko Semet 2021-06-21 01:19:46 +02:00 committed by Marko Semet
parent d6f7aefc30
commit 820cf9df35
2 changed files with 520 additions and 207 deletions

View File

@ -30,26 +30,26 @@ public
{ {
destination.write!(TYPE, Endian.littleEndian)(value, 0); destination.write!(TYPE, Endian.littleEndian)(value, 0);
} }
/++ /++
+ Dumps the value in the system's endian. + Loads the value with little endian.
+ Params: + Params:
+ value = Input value to dump + source = Source buffer
+ destination = Target output buffer
+/ +/
@nogc pure nothrow void dumpNative(TYPE)(immutable TYPE value, ubyte[] destination) @nogc pure nothrow TYPE loadBigEndian(TYPE)(immutable(ubyte)[] source)
in (source.length == TYPE.sizeof)
{ {
static if (endian == Endian.bigEndian) return source.read!(TYPE, Endian.bigEndian)();
{ }
dumpBigEndian(value, destination); /++
} + Dumps the value in big endian.
else static if (endian == Endian.littleEndian) + Params:
{ + source = Source buffer
dumpLittleEndian(value, destination); +/
} @nogc pure nothrow TYPE loadLittleEndian(TYPE)(immutable(ubyte)[] source)
else in (source.length == TYPE.sizeof)
{ {
static assert(false); return source.read!(TYPE, Endian.littleEndian)();
}
} }
} }

View File

@ -5,23 +5,30 @@ private
import core.exception; import core.exception;
import std.array; import std.array;
import std.bitmanip; import std.bitmanip;
import std.conv;
import std.exception; import std.exception;
import std.format;
import std.meta;
import std.range;
import std.string;
import std.system; import std.system;
import std.typecons;
import structs.endian;
/++ /++
+ Supported format types + Supported format types
+/ +/
enum FORMAT_TYPE enum FormatType
{ {
INT_8, /// Signed integer 8 bit int8, /// Signed integer 8 bit
INT_16, /// Signed integer 16 bit int16, /// Signed integer 16 bit
INT_32, /// Signed integer 32 bit int32, /// Signed integer 32 bit
INT_64, /// Signed integer 64 bit int64, /// Signed integer 64 bit
UINT_8, /// Unsigned integer 8 bit uint8, /// Unsigned integer 8 bit
UINT_16, /// Unsigned integer 16 bit uint16, /// Unsigned integer 16 bit
UINT_32, /// Unsigned integer 32 bit uint32, /// Unsigned integer 32 bit
UINT_64, /// Unsigned integer 64 bit uint64, /// Unsigned integer 64 bit
STRING /// C-String str /// C-String
} }
/++ /++
@ -30,151 +37,216 @@ private
struct Element struct Element
{ {
Endian endian; /// The endian to use Endian endian; /// The endian to use
bool is_array; /// If it's an array bool isArray; /// If it's an array
size_t array_size; /// Array size size_t arraySize; /// Array size
FORMAT_TYPE format_type; /// The formated type FormatType formatType; /// The formated type
pure nothrow @nogc size_t baseSize() const
{
final switch (this.formatType)
{
case FormatType.str:
case FormatType.int8:
case FormatType.uint8:
return 1;
case FormatType.int16:
case FormatType.uint16:
return 2;
case FormatType.int32:
case FormatType.uint32:
return 4;
case FormatType.int64:
case FormatType.uint64:
return 8;
}
}
/++ /++
+ Calculate the size of the element + Calculate the size of the element
+ Returns: Size in bytes + Returns: Size in bytes
+/ +/
size_t packSize() const pure nothrow @nogc size_t packSize() const
{ {
// Base format type size // Base format type size
size_t base_size = 0; size_t baseSize = this.baseSize();
final switch (this.format_type)
{
case FORMAT_TYPE.STRING:
case FORMAT_TYPE.INT_8:
case FORMAT_TYPE.UINT_8:
base_size = 1;
break;
case FORMAT_TYPE.INT_16:
case FORMAT_TYPE.UINT_16:
base_size = 2;
break;
case FORMAT_TYPE.INT_32:
case FORMAT_TYPE.UINT_32:
base_size = 4;
break;
case FORMAT_TYPE.INT_64:
case FORMAT_TYPE.UINT_64:
base_size = 8;
break;
}
// Add array size // Add array size
return base_size * this.array_size; return baseSize * this.arraySize;
}
pure Element genBase() const
{
Element result = {};
result.endian = this.endian;
result.isArray = false;
result.arraySize = 1;
result.formatType = this.formatType;
return result;
} }
static pure Element genFromString(string data) static pure Element genFromString(string data)
in (data.length > 0, "Format can't be empty.") in (data.length > 0, "Format can't be empty.")
{ {
// Pre checks // Pre checks
size_t current_pos = 0; size_t currentPos = 0;
// Read endian // Read endian
bool endian_explicit = false; bool endianExplicit = false;
Endian endian_found = std.system.endian; Endian endianFound = std.system.endian;
switch (data[0]) switch (data[0])
{ {
case '=': case '=':
endian_found = std.system.endian; endianFound = std.system.endian;
current_pos++; currentPos++;
endian_explicit = true; endianExplicit = true;
break; break;
case '<': case '<':
endian_found = Endian.littleEndian; endianFound = Endian.littleEndian;
current_pos++; currentPos++;
endian_explicit = true; endianExplicit = true;
break; break;
case '!': case '!':
case '>': case '>':
endian_found = Endian.bigEndian; endianFound = Endian.bigEndian;
current_pos++; currentPos++;
endian_explicit = true; endianExplicit = true;
break; break;
default: default:
} }
// Get size // Get size
bool set_size = false; bool setSize = false;
size_t size = 0; size_t size = 0;
while ((current_pos < data.length) && ('0' <= data[current_pos]) && (data[current_pos] <= '9')) while ((currentPos < data.length) && ('0' <= data[currentPos]) && (data[currentPos] <= '9'))
{ {
set_size = true; setSize = true;
size = (size * 10) + (data[current_pos] - '0'); size = (size * 10) + (data[currentPos] - '0');
current_pos++; currentPos++;
} }
if (!set_size) if (!setSize)
{ {
size = 1; size = 1;
} }
// Get format // Get format
assert(current_pos + 1 == data.length, "Format '" ~ data[current_pos .. $] ~ "' isn't a valid format type."); assert(currentPos + 1 == data.length, "Format '" ~ data[currentPos .. $] ~ "' isn't a valid format type.");
FORMAT_TYPE format_type_found; FormatType formatTypeFound;
switch (data[current_pos]) switch (data[currentPos])
{ {
case 'b': case 'b':
format_type_found = FORMAT_TYPE.INT_8; formatTypeFound = FormatType.int8;
current_pos++; currentPos++;
break; break;
case 'B': case 'B':
format_type_found = FORMAT_TYPE.UINT_8; formatTypeFound = FormatType.uint8;
current_pos++; currentPos++;
break; break;
case 'h': case 'h':
format_type_found = FORMAT_TYPE.INT_16; formatTypeFound = FormatType.int16;
current_pos++; currentPos++;
break; break;
case 'H': case 'H':
format_type_found = FORMAT_TYPE.UINT_16; formatTypeFound = FormatType.uint16;
current_pos++; currentPos++;
break; break;
case 'i': case 'i':
format_type_found = FORMAT_TYPE.INT_32; formatTypeFound = FormatType.int32;
current_pos++; currentPos++;
break; break;
case 'I': case 'I':
format_type_found = FORMAT_TYPE.UINT_32; formatTypeFound = FormatType.uint32;
current_pos++; currentPos++;
break; break;
case 'q': case 'q':
format_type_found = FORMAT_TYPE.INT_64; formatTypeFound = FormatType.int64;
current_pos++; currentPos++;
break; break;
case 'Q': case 'Q':
format_type_found = FORMAT_TYPE.UINT_64; formatTypeFound = FormatType.uint64;
current_pos++; currentPos++;
break; break;
case 's': case 's':
assert(set_size, "Size have to be set for the string."); assert(setSize, "Size have to be set for the string.");
format_type_found = FORMAT_TYPE.STRING; formatTypeFound = FormatType.str;
current_pos++; currentPos++;
break; break;
default: default:
assert(false, "Unknown format string: '" ~ data[current_pos .. $] ~ "'"); assert(false, "Unknown format string: '" ~ data[currentPos .. $] ~ "'");
} }
// TODO: Warn if endian isn't set explicit // Check endians
assert((formatTypeFound != FormatType.str) || !endianExplicit, "Endian isn't allowed for strings."); // Is string ==> not endian set
assert(((formatTypeFound != FormatType.int8) && (formatTypeFound != FormatType.uint8)
&& (formatTypeFound != FormatType.str))
|| !endianExplicit, "Endian isn't allowed for (unsigned) bytes or strings."); // Is byte ==> not endian set
assert((formatTypeFound == FormatType.str) || (formatTypeFound == FormatType.int8)
|| (formatTypeFound == FormatType.uint8)
|| endianExplicit, "Endian is required for non string or (unsigned) char."); // Is not string, int8 or uint8 ==> endian set
// Return new struct // Return new struct
assert(current_pos == data.length); assert(currentPos == data.length);
Element result = {endian: endian_found, is_array: set_size, array_size: size, format_type: format_type_found}; Element result = {endian: endianFound, isArray: setSize, arraySize: size, formatType: formatTypeFound};
return result; return result;
} }
} }
template GET_TYPE(Element ELEMENT) /++
+ Generate type of an element
+/
template GetType(Element ELEMENT)
{ {
static if (ELEEMENT.format_type == FORMAT_TYPE.STRING) static if (ELEMENT.formatType == FormatType.str)
{ {
alias GET_TYPE = string; alias GetType = string;
} }
else else
{ {
static assert(false); // Get type
static if (ELEMENT.formatType == FormatType.int8)
{
alias TMP = byte;
}
else static if (ELEMENT.formatType == FormatType.int16)
{
alias TMP = short;
}
else static if (ELEMENT.formatType == FormatType.int32)
{
alias TMP = int;
}
else static if (ELEMENT.formatType == FormatType.int64)
{
alias TMP = long;
}
else static if (ELEMENT.formatType == FormatType.uint8)
{
alias TMP = ubyte;
}
else static if (ELEMENT.formatType == FormatType.uint16)
{
alias TMP = ushort;
}
else static if (ELEMENT.formatType == FormatType.uint32)
{
alias TMP = uint;
}
else static if (ELEMENT.formatType == FormatType.uint64)
{
alias TMP = ulong;
}
else
{
static assert(false);
}
// Is array
static if (ELEMENT.isArray)
{
alias GetType = TMP[ELEMENT.arraySize];
}
else
{
alias GetType = TMP;
}
} }
} }
@ -184,15 +256,15 @@ private
+ source = The source string to format + source = The source string to format
+ Returns: String without whitespaces + Returns: String without whitespaces
+/ +/
pure string remove_whitespaces(string source) pure string removeWhitespaces(string source)
{ {
return source.replace(" ", ""); return source.replace(" ", "");
} }
pure Element[] parse_string(string source) pure Element[] parseString(string source)
{ {
// Remove whitespaces // Remove whitespaces
source = remove_whitespaces(source); source = removeWhitespaces(source);
// Split after char // Split after char
Element[] elements = []; Element[] elements = [];
@ -214,67 +286,61 @@ private
// Test remove whitespaces. // Test remove whitespaces.
unittest unittest
{ {
assert(remove_whitespaces(" a b c ") == "abc"); assert(removeWhitespaces(" a b c ") == "abc");
} }
// Test endian and length // Test endian and length
unittest unittest
{ {
{ assertThrown!AssertError(Element.genFromString("i"));
const auto tmp = Element.genFromString("i");
assert(tmp.endian == endian);
assert(tmp.is_array == false);
assert(tmp.array_size == 1);
assert(tmp.format_type == FORMAT_TYPE.INT_32);
}
{ {
const auto tmp = Element.genFromString("=i"); const auto tmp = Element.genFromString("=i");
assert(tmp.endian == endian); assert(tmp.endian == endian);
assert(tmp.is_array == false); assert(tmp.isArray == false);
assert(tmp.array_size == 1); assert(tmp.arraySize == 1);
assert(tmp.format_type == FORMAT_TYPE.INT_32); assert(tmp.formatType == FormatType.int32);
} }
{ {
const auto tmp = Element.genFromString(">i"); const auto tmp = Element.genFromString(">i");
assert(tmp.endian == Endian.bigEndian); assert(tmp.endian == Endian.bigEndian);
assert(tmp.is_array == false); assert(tmp.isArray == false);
assert(tmp.array_size == 1); assert(tmp.arraySize == 1);
assert(tmp.format_type == FORMAT_TYPE.INT_32); assert(tmp.formatType == FormatType.int32);
} }
{ {
const auto tmp = Element.genFromString("!i"); const auto tmp = Element.genFromString("!i");
assert(tmp.endian == Endian.bigEndian); assert(tmp.endian == Endian.bigEndian);
assert(tmp.is_array == false); assert(tmp.isArray == false);
assert(tmp.array_size == 1); assert(tmp.arraySize == 1);
assert(tmp.format_type == FORMAT_TYPE.INT_32); assert(tmp.formatType == FormatType.int32);
} }
{ {
const auto tmp = Element.genFromString("<i"); const auto tmp = Element.genFromString("<i");
assert(tmp.endian == Endian.littleEndian); assert(tmp.endian == Endian.littleEndian);
assert(tmp.is_array == false); assert(tmp.isArray == false);
assert(tmp.array_size == 1); assert(tmp.arraySize == 1);
assert(tmp.format_type == FORMAT_TYPE.INT_32); assert(tmp.formatType == FormatType.int32);
} }
{ {
const auto tmp = Element.genFromString("<1i"); const auto tmp = Element.genFromString("<1i");
assert(tmp.endian == Endian.littleEndian); assert(tmp.endian == Endian.littleEndian);
assert(tmp.is_array == true); assert(tmp.isArray == true);
assert(tmp.array_size == 1); assert(tmp.arraySize == 1);
assert(tmp.format_type == FORMAT_TYPE.INT_32); assert(tmp.formatType == FormatType.int32);
} }
{ {
const auto tmp = Element.genFromString("<11i"); const auto tmp = Element.genFromString("<11i");
assert(tmp.endian == Endian.littleEndian); assert(tmp.endian == Endian.littleEndian);
assert(tmp.is_array == true); assert(tmp.isArray == true);
assert(tmp.array_size == 11); assert(tmp.arraySize == 11);
assert(tmp.format_type == FORMAT_TYPE.INT_32); assert(tmp.formatType == FormatType.int32);
} }
{ {
const auto tmp = Element.genFromString("<111i"); const auto tmp = Element.genFromString("<111i");
assert(tmp.endian == Endian.littleEndian); assert(tmp.endian == Endian.littleEndian);
assert(tmp.is_array == true); assert(tmp.isArray == true);
assert(tmp.array_size == 111); assert(tmp.arraySize == 111);
assert(tmp.format_type == FORMAT_TYPE.INT_32); assert(tmp.formatType == FormatType.int32);
} }
} }
@ -282,67 +348,67 @@ private
unittest unittest
{ {
{ {
const auto tmp = Element.genFromString("<b"); const auto tmp = Element.genFromString("b");
assert(tmp.endian == Endian.littleEndian); assert(tmp.endian == Endian.littleEndian);
assert(tmp.is_array == false); assert(tmp.isArray == false);
assert(tmp.array_size == 1); assert(tmp.arraySize == 1);
assert(tmp.format_type == FORMAT_TYPE.INT_8); assert(tmp.formatType == FormatType.int8);
} }
{ {
const auto tmp = Element.genFromString("<B"); const auto tmp = Element.genFromString("B");
assert(tmp.endian == Endian.littleEndian); assert(tmp.endian == Endian.littleEndian);
assert(tmp.is_array == false); assert(tmp.isArray == false);
assert(tmp.array_size == 1); assert(tmp.arraySize == 1);
assert(tmp.format_type == FORMAT_TYPE.UINT_8); assert(tmp.formatType == FormatType.uint8);
} }
{ {
const auto tmp = Element.genFromString("<h"); const auto tmp = Element.genFromString("<h");
assert(tmp.endian == Endian.littleEndian); assert(tmp.endian == Endian.littleEndian);
assert(tmp.is_array == false); assert(tmp.isArray == false);
assert(tmp.array_size == 1); assert(tmp.arraySize == 1);
assert(tmp.format_type == FORMAT_TYPE.INT_16); assert(tmp.formatType == FormatType.int16);
} }
{ {
const auto tmp = Element.genFromString("<H"); const auto tmp = Element.genFromString("<H");
assert(tmp.endian == Endian.littleEndian); assert(tmp.endian == Endian.littleEndian);
assert(tmp.is_array == false); assert(tmp.isArray == false);
assert(tmp.array_size == 1); assert(tmp.arraySize == 1);
assert(tmp.format_type == FORMAT_TYPE.UINT_16); assert(tmp.formatType == FormatType.uint16);
} }
{ {
const auto tmp = Element.genFromString("<i"); const auto tmp = Element.genFromString("<i");
assert(tmp.endian == Endian.littleEndian); assert(tmp.endian == Endian.littleEndian);
assert(tmp.is_array == false); assert(tmp.isArray == false);
assert(tmp.array_size == 1); assert(tmp.arraySize == 1);
assert(tmp.format_type == FORMAT_TYPE.INT_32); assert(tmp.formatType == FormatType.int32);
} }
{ {
const auto tmp = Element.genFromString("<I"); const auto tmp = Element.genFromString("<I");
assert(tmp.endian == Endian.littleEndian); assert(tmp.endian == Endian.littleEndian);
assert(tmp.is_array == false); assert(tmp.isArray == false);
assert(tmp.array_size == 1); assert(tmp.arraySize == 1);
assert(tmp.format_type == FORMAT_TYPE.UINT_32); assert(tmp.formatType == FormatType.uint32);
} }
{ {
const auto tmp = Element.genFromString("<q"); const auto tmp = Element.genFromString("<q");
assert(tmp.endian == Endian.littleEndian); assert(tmp.endian == Endian.littleEndian);
assert(tmp.is_array == false); assert(tmp.isArray == false);
assert(tmp.array_size == 1); assert(tmp.arraySize == 1);
assert(tmp.format_type == FORMAT_TYPE.INT_64); assert(tmp.formatType == FormatType.int64);
} }
{ {
const auto tmp = Element.genFromString("<Q"); const auto tmp = Element.genFromString("<Q");
assert(tmp.endian == Endian.littleEndian); assert(tmp.endian == Endian.littleEndian);
assert(tmp.is_array == false); assert(tmp.isArray == false);
assert(tmp.array_size == 1); assert(tmp.arraySize == 1);
assert(tmp.format_type == FORMAT_TYPE.UINT_64); assert(tmp.formatType == FormatType.uint64);
} }
{ {
const auto tmp = Element.genFromString("<10s"); const auto tmp = Element.genFromString("10s");
assert(tmp.endian == Endian.littleEndian); assert(tmp.endian == Endian.littleEndian);
assert(tmp.is_array == true); assert(tmp.isArray == true);
assert(tmp.array_size == 10); assert(tmp.arraySize == 10);
assert(tmp.format_type == FORMAT_TYPE.STRING); assert(tmp.formatType == FormatType.str);
} }
assertThrown!AssertError(Element.genFromString("s")); assertThrown!AssertError(Element.genFromString("s"));
assertThrown!AssertError(Element.genFromString("Z")); assertThrown!AssertError(Element.genFromString("Z"));
@ -352,31 +418,91 @@ private
unittest unittest
{ {
{ {
const auto tmp = parse_string("<16i>16i=16i!16i"); const auto tmp = parseString("<16i>16i=16i!16i");
foreach (i; tmp) foreach (i; tmp)
{ {
assert(i.is_array == true); assert(i.isArray == true);
assert(i.array_size == 16); assert(i.arraySize == 16);
assert(i.format_type == FORMAT_TYPE.INT_32); assert(i.formatType == FormatType.int32);
} }
} }
assertThrown!AssertError(parse_string("i16")); assertThrown!AssertError(parseString("i16"));
assertThrown!AssertError(parse_string("i!")); assertThrown!AssertError(parseString("i!"));
assertThrown!AssertError(parse_string("16!i")); assertThrown!AssertError(parseString("16!i"));
}
// Test helper functions
unittest
{
{
Element tmp = {};
tmp.arraySize = 10;
tmp.endian = Endian.littleEndian;
tmp.formatType = FormatType.int8;
tmp.isArray = true;
const Element test = tmp.genBase();
assert(test.arraySize == 1);
assert(test.endian == Endian.littleEndian);
assert(test.formatType == FormatType.int8);
assert(test.isArray == false);
assert(tmp.arraySize == 10);
assert(tmp.endian == Endian.littleEndian);
assert(tmp.formatType == FormatType.int8);
assert(tmp.isArray == true);
}
} }
} }
public public
{ {
/++ /++
+ Base format type + A format error when formating wasn't possible.
+/
class FormatError : Exception
{
/++
+ Constructor of an format error.
+ Params:
+ msg = Error message.
+ nextInChain = Next error in the chain.
+/
this(string msg, Throwable nextInChain = null) pure nothrow @nogc @safe
{
super(msg, nextInChain);
}
}
/++
+ Base format type.
+
+ A format string item contains of free options: "[Endian][Array size][Type]"
+
+ Endian can be "<" for little endian; ">", "!" for big endian; and "=" for native (highly not recommanded).
+
+ Array size is optional except for c-strings.
+
+ The type can be:
+ - "b"/"B" for a signed 8 bit integer. Setting an endian isn't allowed.
+ - "h"/"H" for a signed 16 bit integer.
+ - "i"/"I" for an 32 bit integer.
+ - "q"/"Q" for a signed 64 bit integer.
+ - "s" for a c-string. The array size descripes the maximal length of the c-string including the terminating
+ zero byte. Should the string be smaller then the given length will be expected (and gerated) additnal zero
+ bytes after the end of the string. Should the string be to long a exception will be thrown. Should a c-string
+ doesn't end with a valid zero byte during unpacking an exception will be thrown. Should during unpacking of
+ a c-string after the first zero byte follow additinal non zero-bytes inside the reservated space will be the
+ additional data be ignored. During unpacking a c-string will be the tailing zero byte removed and during
+ be added. Setting an endian isn't allowed.
+
+ Macros:
+ CONFIG = The config string of the byte format.
+/ +/
struct BaseFormat(string CONFIG) struct BaseFormat(string CONFIG)
{ {
private private
{ {
static const(Element[]) elements = parse_string(CONFIG); static immutable(Element[]) elements = parseString(CONFIG);
static const(size_t) elements_size = { static immutable(size_t) elementsSize = {
size_t size = 0; size_t size = 0;
static foreach (i; elements) static foreach (i; elements)
{ {
@ -384,57 +510,244 @@ public
} }
return size; return size;
}(); }();
static pure void unpackEntry(Element ELEMENT)(ref GetType!ELEMENT target, immutable(ubyte)[] source)
in (source.length == ELEMENT.packSize())do
{
static if (ELEMENT.formatType == FormatType.str)
{
// Get length
size_t len = 0;
foreach (i; 0 .. source.length)
{
if (source[i] == 0)
{
break;
}
len++;
}
if (len >= ELEMENT.arraySize)
{
throw new FormatError(format!"C-String of size %d had no terminating zero byte."(ELEMENT
.arraySize));
}
// Output string
target = (cast(immutable(char[])) source[0 .. len]);
return;
}
else static if (ELEMENT.isArray)
{
static if (ELEMENT.endian == Endian.littleEndian)
{
static foreach (i; 0 .. ELEMENT.arraySize)
{
target[i] = loadLittleEndian!(GetType!(ELEMENT.genBase()))(
source[(i * ELEMENT.baseSize()) .. ((i + 1) * ELEMENT.baseSize())]);
}
return;
}
else static if (ELEMENT.endian == Endian.bigEndian)
{
static foreach (i; 0 .. ELEMENT.arraySize)
{
target[i] = loadBigEndian!(GetType!(ELEMENT.genBase()))(
source[(i * ELEMENT.baseSize()) .. ((i + 1) * ELEMENT.baseSize())]);
}
return;
}
else
{
static assert(false); // Should never happen
}
}
else
{
static if (ELEMENT.endian == Endian.littleEndian)
{
target = loadLittleEndian!(GetType!ELEMENT)(source);
return;
}
else static if (ELEMENT.endian == Endian.bigEndian)
{
target = loadBigEndian!(GetType!ELEMENT)(source);
return;
}
else
{
static assert(false); // Should never happen
}
}
}
mixin({
string result = "alias ARGS = staticMap!(GetType";
foreach (i; 0 .. elements.length)
{
result ~= ", elements[" ~ to!string(i) ~ "]";
}
return result ~ ");";
}());
} }
public public
{ {
alias TUPLE = Tuple!(ARGS);
/++ /++
+ Calculate the size of the format. + Calculate the size of the format.
+ Returns: Size of the format + Returns: Size of the format
+/ +/
static size_t size() static pure nothrow @nogc size_t size()
{ {
return elements_size; return elementsSize;
}
/++
+
+/
static pure TUPLE unpack(immutable(ubyte)[] source)
{
// Check input
if (source.length != size())
{
throw new FormatError(format!"Source has size %d but %d is required."(source.length, size()));
}
// Generate output
auto result = TUPLE();
size_t pos = 0;
static foreach (i; 0 .. elements.length)
{
unpackEntry!(elements[i])(result[i], source[pos .. pos + elements[i].packSize()]);
pos += elements[i].packSize();
}
return result;
} }
} }
} }
/++
+ Is a integer of type size_t with the length of base format string.
+ For a detailed documentation of a format string look at [BaseFormat].
+/
template baseSize(string FORMAT)
{
static const size_t baseSize = BaseFormat!FORMAT.size();
}
/++
+ Unpacks a base format string.
+ For a detailed documentation of a format string look at [BaseFormat].
+/
pure BaseFormat!FORMAT.TUPLE baseUnpack(string FORMAT)(immutable(ubyte)[] source)
{
return BaseFormat!FORMAT.unpack(source);
}
} }
private private
{ {
size_t test_size(string FORMAT)() // Test size
{
size_t result = BaseFormat!FORMAT.size();
{
auto tmp = parse_string(FORMAT);
size_t calced = 0;
foreach (i; tmp)
{
calced += i.packSize();
}
assert(result == calced);
}
return result;
}
unittest unittest
{ {
assert(test_size!"10s"() == 10); size_t testSize(string FORMAT)()
assert(test_size!"b"() == 1); {
assert(test_size!"4b"() == 4); size_t result = BaseFormat!FORMAT.size();
assert(test_size!"B"() == 1); assert(baseSize!FORMAT == result);
assert(test_size!"4B"() == 4); {
assert(test_size!"h"() == 2); auto tmp = parseString(FORMAT);
assert(test_size!"4h"() == 8); size_t calced = 0;
assert(test_size!"H"() == 2); foreach (i; tmp)
assert(test_size!"4H"() == 8); {
assert(test_size!"i"() == 4); calced += i.packSize();
assert(test_size!"4i"() == 16); }
assert(test_size!"I"() == 4); assert(result == calced);
assert(test_size!"4I"() == 16); }
assert(test_size!"q"() == 8); return result;
assert(test_size!"4q"() == 32); }
assert(test_size!"Q"() == 8);
assert(test_size!"4Q"() == 32); assert(testSize!"10s"() == 10);
assert(testSize!"b"() == 1);
assert(testSize!"4b"() == 4);
assert(testSize!"B"() == 1);
assert(testSize!"4B"() == 4);
assert(testSize!">h"() == 2);
assert(testSize!">4h"() == 8);
assert(testSize!">H"() == 2);
assert(testSize!">4H"() == 8);
assert(testSize!">i"() == 4);
assert(testSize!">4i"() == 16);
assert(testSize!">I"() == 4);
assert(testSize!">4I"() == 16);
assert(testSize!">q"() == 8);
assert(testSize!">4q"() == 32);
assert(testSize!">Q"() == 8);
assert(testSize!">4Q"() == 32);
}
// Test pack and unpack
unittest
{
template testPacking(string FORMAT)
{
void testPacking(TUPLE)(TUPLE data, immutable(ubyte[]) packed)
{
assert(BaseFormat!FORMAT.unpack(packed) == data);
assert(baseUnpack!FORMAT(packed) == data);
}
}
// Strings
testPacking!"3s"(tuple(""), [0, 0, 0]);
testPacking!"3s"(tuple("a"), ['a', 0, 0]);
testPacking!"3s"(tuple("ab"), ['a', 'b', 0]);
assertThrown!FormatError(BaseFormat!"3s".unpack(['a', 'b', 'c']));
// Bytes
testPacking!"b"(tuple(1), [1]);
testPacking!"b"(tuple(-1), [255]);
testPacking!"B"(tuple(255), [255]);
// Shorts
testPacking!"<h"(tuple(1), [1, 0]);
testPacking!"<h"(tuple(-1), [255, 255]);
testPacking!">h"(tuple(1), [0, 1]);
testPacking!">h"(tuple(-1), [255, 255]);
testPacking!"<H"(tuple(0xFFFE), [0xFE, 0xFF]);
testPacking!">H"(tuple(0xFFFE), [0xFF, 0xFE]);
// Integers
testPacking!"<i"(tuple(1), [1, 0, 0, 0]);
testPacking!"<i"(tuple(-1), [255, 255, 255, 255]);
testPacking!">i"(tuple(1), [0, 0, 0, 1]);
testPacking!">i"(tuple(-1), [255, 255, 255, 255]);
testPacking!"<I"(tuple(0xFFFFFFFE), [0xFE, 0xFF, 0xFF, 0xFF]);
testPacking!">I"(tuple(0xFFFFFFFE), [0xFF, 0xFF, 0xFF, 0xFE]);
// Longs
testPacking!"<q"(tuple(1), [1, 0, 0, 0, 0, 0, 0, 0]);
testPacking!"<q"(tuple(-1), [255, 255, 255, 255, 255, 255, 255, 255]);
testPacking!">q"(tuple(1), [0, 0, 0, 0, 0, 0, 0, 1]);
testPacking!">q"(tuple(-1), [255, 255, 255, 255, 255, 255, 255, 255]);
testPacking!"<Q"(tuple(0xFFFFFFFFFFFFFFFE), [0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
testPacking!">Q"(tuple(0xFFFFFFFFFFFFFFFE), [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE]);
// Array
testPacking!"<1i"(tuple([1]), [1, 0, 0, 0]);
testPacking!">1i"(tuple([1]), [0, 0, 0, 1]);
testPacking!"<4i"(tuple([1, 2, 3, 4]), [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0]);
testPacking!">4i"(tuple([1, 2, 3, 4]), [0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4]);
// Multiple types after each other
testPacking!"<4i>Q"(tuple([1, 2, 3, 4], 0xFFFFFFFFFFFFFFFE), [
1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE
]);
testPacking!"<Q>4i"(tuple(0xFFFFFFFFFFFFFFFE, [1, 2, 3, 4]), [
0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4
]);
// Except size error
assertThrown!FormatError(testPacking!">Q"(tuple(0xFFFFFFFFFFFFFFFE), [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]));
} }
} }