diff --git a/source/structs/endian.d b/source/structs/endian.d index 592375c..c69e1f6 100644 --- a/source/structs/endian.d +++ b/source/structs/endian.d @@ -30,26 +30,26 @@ public { destination.write!(TYPE, Endian.littleEndian)(value, 0); } + /++ - + Dumps the value in the system's endian. + + Loads the value with little endian. + Params: - + value = Input value to dump - + destination = Target output buffer + + source = Source 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) - { - dumpBigEndian(value, destination); - } - else static if (endian == Endian.littleEndian) - { - dumpLittleEndian(value, destination); - } - else - { - static assert(false); - } + return source.read!(TYPE, Endian.bigEndian)(); + } + /++ + + Dumps the value in big endian. + + Params: + + source = Source buffer + +/ + @nogc pure nothrow TYPE loadLittleEndian(TYPE)(immutable(ubyte)[] source) + in (source.length == TYPE.sizeof) + { + return source.read!(TYPE, Endian.littleEndian)(); } } diff --git a/source/structs/package.d b/source/structs/package.d index a52282d..fbb37c9 100644 --- a/source/structs/package.d +++ b/source/structs/package.d @@ -5,23 +5,30 @@ private import core.exception; import std.array; import std.bitmanip; + import std.conv; import std.exception; + import std.format; + import std.meta; + import std.range; + import std.string; import std.system; + import std.typecons; + import structs.endian; /++ + Supported format types +/ - enum FORMAT_TYPE + enum FormatType { - INT_8, /// Signed integer 8 bit - INT_16, /// Signed integer 16 bit - INT_32, /// Signed integer 32 bit - INT_64, /// Signed integer 64 bit - UINT_8, /// Unsigned integer 8 bit - UINT_16, /// Unsigned integer 16 bit - UINT_32, /// Unsigned integer 32 bit - UINT_64, /// Unsigned integer 64 bit - STRING /// C-String + int8, /// Signed integer 8 bit + int16, /// Signed integer 16 bit + int32, /// Signed integer 32 bit + int64, /// Signed integer 64 bit + uint8, /// Unsigned integer 8 bit + uint16, /// Unsigned integer 16 bit + uint32, /// Unsigned integer 32 bit + uint64, /// Unsigned integer 64 bit + str /// C-String } /++ @@ -30,151 +37,216 @@ private struct Element { Endian endian; /// The endian to use - bool is_array; /// If it's an array - size_t array_size; /// Array size - FORMAT_TYPE format_type; /// The formated type + bool isArray; /// If it's an array + size_t arraySize; /// Array size + 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 + Returns: Size in bytes +/ - size_t packSize() const + pure nothrow @nogc size_t packSize() const { // Base format type size - size_t base_size = 0; - 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; - } + size_t baseSize = this.baseSize(); // 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) in (data.length > 0, "Format can't be empty.") { // Pre checks - size_t current_pos = 0; + size_t currentPos = 0; // Read endian - bool endian_explicit = false; - Endian endian_found = std.system.endian; + bool endianExplicit = false; + Endian endianFound = std.system.endian; switch (data[0]) { case '=': - endian_found = std.system.endian; - current_pos++; - endian_explicit = true; + endianFound = std.system.endian; + currentPos++; + endianExplicit = true; break; case '<': - endian_found = Endian.littleEndian; - current_pos++; - endian_explicit = true; + endianFound = Endian.littleEndian; + currentPos++; + endianExplicit = true; break; case '!': - case '>': - endian_found = Endian.bigEndian; - current_pos++; - endian_explicit = true; + case '>': + endianFound = Endian.bigEndian; + currentPos++; + endianExplicit = true; break; default: } // Get size - bool set_size = false; + bool setSize = false; 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; - size = (size * 10) + (data[current_pos] - '0'); - current_pos++; + setSize = true; + size = (size * 10) + (data[currentPos] - '0'); + currentPos++; } - if (!set_size) + if (!setSize) { size = 1; } // Get format - assert(current_pos + 1 == data.length, "Format '" ~ data[current_pos .. $] ~ "' isn't a valid format type."); - FORMAT_TYPE format_type_found; - switch (data[current_pos]) + assert(currentPos + 1 == data.length, "Format '" ~ data[currentPos .. $] ~ "' isn't a valid format type."); + FormatType formatTypeFound; + switch (data[currentPos]) { case 'b': - format_type_found = FORMAT_TYPE.INT_8; - current_pos++; + formatTypeFound = FormatType.int8; + currentPos++; break; case 'B': - format_type_found = FORMAT_TYPE.UINT_8; - current_pos++; + formatTypeFound = FormatType.uint8; + currentPos++; break; case 'h': - format_type_found = FORMAT_TYPE.INT_16; - current_pos++; + formatTypeFound = FormatType.int16; + currentPos++; break; case 'H': - format_type_found = FORMAT_TYPE.UINT_16; - current_pos++; + formatTypeFound = FormatType.uint16; + currentPos++; break; case 'i': - format_type_found = FORMAT_TYPE.INT_32; - current_pos++; + formatTypeFound = FormatType.int32; + currentPos++; break; case 'I': - format_type_found = FORMAT_TYPE.UINT_32; - current_pos++; + formatTypeFound = FormatType.uint32; + currentPos++; break; case 'q': - format_type_found = FORMAT_TYPE.INT_64; - current_pos++; + formatTypeFound = FormatType.int64; + currentPos++; break; case 'Q': - format_type_found = FORMAT_TYPE.UINT_64; - current_pos++; + formatTypeFound = FormatType.uint64; + currentPos++; break; case 's': - assert(set_size, "Size have to be set for the string."); - format_type_found = FORMAT_TYPE.STRING; - current_pos++; + assert(setSize, "Size have to be set for the string."); + formatTypeFound = FormatType.str; + currentPos++; break; 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 - assert(current_pos == data.length); - Element result = {endian: endian_found, is_array: set_size, array_size: size, format_type: format_type_found}; + assert(currentPos == data.length); + Element result = {endian: endianFound, isArray: setSize, arraySize: size, formatType: formatTypeFound}; 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 { - 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 + Returns: String without whitespaces +/ - pure string remove_whitespaces(string source) + pure string removeWhitespaces(string source) { return source.replace(" ", ""); } - pure Element[] parse_string(string source) + pure Element[] parseString(string source) { // Remove whitespaces - source = remove_whitespaces(source); + source = removeWhitespaces(source); // Split after char Element[] elements = []; @@ -214,67 +286,61 @@ private // Test remove whitespaces. unittest { - assert(remove_whitespaces(" a b c ") == "abc"); + assert(removeWhitespaces(" a b c ") == "abc"); } // Test endian and length unittest { - { - 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); - } + 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); + assert(tmp.isArray == false); + assert(tmp.arraySize == 1); + assert(tmp.formatType == FormatType.int32); } { const auto tmp = Element.genFromString(">i"); assert(tmp.endian == Endian.bigEndian); - assert(tmp.is_array == false); - assert(tmp.array_size == 1); - assert(tmp.format_type == FORMAT_TYPE.INT_32); + assert(tmp.isArray == false); + assert(tmp.arraySize == 1); + assert(tmp.formatType == FormatType.int32); } { const auto tmp = Element.genFromString("!i"); assert(tmp.endian == Endian.bigEndian); - assert(tmp.is_array == false); - assert(tmp.array_size == 1); - assert(tmp.format_type == FORMAT_TYPE.INT_32); + assert(tmp.isArray == false); + assert(tmp.arraySize == 1); + assert(tmp.formatType == FormatType.int32); } { const auto tmp = Element.genFromString("16i=16i!16i"); + const auto tmp = parseString("<16i>16i=16i!16i"); foreach (i; tmp) { - assert(i.is_array == true); - assert(i.array_size == 16); - assert(i.format_type == FORMAT_TYPE.INT_32); + assert(i.isArray == true); + assert(i.arraySize == 16); + assert(i.formatType == FormatType.int32); } } - assertThrown!AssertError(parse_string("i16")); - assertThrown!AssertError(parse_string("i!")); - assertThrown!AssertError(parse_string("16!i")); + assertThrown!AssertError(parseString("i16")); + assertThrown!AssertError(parseString("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 { /++ - + 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) { private { - static const(Element[]) elements = parse_string(CONFIG); - static const(size_t) elements_size = { + static immutable(Element[]) elements = parseString(CONFIG); + static immutable(size_t) elementsSize = { size_t size = 0; static foreach (i; elements) { @@ -384,57 +510,244 @@ public } 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 { + alias TUPLE = Tuple!(ARGS); + /++ + Calculate the 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 { - size_t test_size(string FORMAT)() - { - 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; - } - + // Test size unittest { - assert(test_size!"10s"() == 10); - assert(test_size!"b"() == 1); - assert(test_size!"4b"() == 4); - assert(test_size!"B"() == 1); - assert(test_size!"4B"() == 4); - assert(test_size!"h"() == 2); - assert(test_size!"4h"() == 8); - assert(test_size!"H"() == 2); - assert(test_size!"4H"() == 8); - assert(test_size!"i"() == 4); - assert(test_size!"4i"() == 16); - assert(test_size!"I"() == 4); - assert(test_size!"4I"() == 16); - assert(test_size!"q"() == 8); - assert(test_size!"4q"() == 32); - assert(test_size!"Q"() == 8); - assert(test_size!"4Q"() == 32); + size_t testSize(string FORMAT)() + { + size_t result = BaseFormat!FORMAT.size(); + assert(baseSize!FORMAT == result); + { + auto tmp = parseString(FORMAT); + size_t calced = 0; + foreach (i; tmp) + { + calced += i.packSize(); + } + assert(result == calced); + } + return result; + } + + 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), [0, 1]); + testPacking!">h"(tuple(-1), [255, 255]); + testPacking!"H"(tuple(0xFFFE), [0xFF, 0xFE]); + + // Integers + testPacking!"i"(tuple(1), [0, 0, 0, 1]); + testPacking!">i"(tuple(-1), [255, 255, 255, 255]); + testPacking!"I"(tuple(0xFFFFFFFE), [0xFF, 0xFF, 0xFF, 0xFE]); + + // Longs + 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), [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!"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])); } }