1 /** 2 * Templates for easily parsing headers using structs 3 */ 4 module daffodil.util.headers; 5 6 import std.array; 7 import std.meta; 8 import std.traits; 9 import std.bitmanip; 10 import std.algorithm; 11 12 import daffodil.util.range; 13 import daffodil.util.errors; 14 15 /** 16 * Attribute signifying the endianess of a field or type 17 */ 18 enum Endianess { 19 little, 20 big, 21 } 22 23 private template endianess(alias field, Endianess default_) { 24 template isEndianess(alias E) { 25 enum isEndianess = is(typeof(E) == Endianess); 26 } 27 28 // Use either the given default or the last attribute 29 alias endianessAttrs = Filter!(isEndianess, __traits(getAttributes, field)); 30 static if (endianessAttrs.length > 0) { 31 enum endianess = endianessAttrs[$-1]; 32 } else { 33 enum endianess = default_; 34 } 35 } 36 37 /** 38 * Documentation 39 */ 40 template convertable(T) { 41 enum convertable = isIntegral!T || isSomeChar!T || isBoolean!T || 42 (__traits(isPOD, T) && isAggregateType!T); 43 } 44 45 /** 46 * Given a type and endianess, convert a input range of ubyte to that type. 47 * Does not support any dynamically sized types or non-standard alignments 48 */ 49 T parseHeader(T, Endianess e = Endianess.little, R)(R data) if (convertable!T && 50 isInputRange!R && 51 is(ElementType!R == ubyte)) { 52 static if (isAggregateType!T) { 53 T value; 54 55 foreach (field; FieldNameTuple!T) { 56 enum member = "value."~field; 57 enum endian = endianess!(mixin(member), e); 58 mixin(member~" = parseHeader!(typeof("~member~"), endian)(data);"); 59 } 60 return value; 61 } else { 62 auto taken = data.take(T.sizeof).array; 63 enforce!UnexpectedEndOfData(taken.length == T.sizeof); 64 65 ubyte[T.sizeof] fieldData = taken.array[0..T.sizeof]; 66 67 static if (e is Endianess.little) { 68 return littleEndianToNative!T(fieldData); 69 } else { 70 return bigEndianToNative!T(fieldData); 71 } 72 } 73 } 74 75 @("Able to parse headers") 76 unittest { 77 static struct Data { 78 ushort field1; 79 @(Endianess.little) 80 ushort field2; 81 } 82 83 ubyte[] data = [0xDE, 0xAD, 0xAD, 0xDE]; 84 85 Data d = parseHeader!(Data, Endianess.big)(data.iter); 86 assert(d.field1 == 0xDEAD); 87 assert(d.field2 == 0xDEAD); 88 } 89 90 /** 91 * Given a instance and default endianess, write that instance to a output range of ubyte. 92 */ 93 void writeHeader(Endianess e = Endianess.little, T, R)(T value, R output) if (convertable!T && 94 isOutputRange!(R, ubyte)) { 95 static if (isAggregateType!T) { 96 foreach (field; FieldNameTuple!T) { 97 enum member = "value."~field; 98 enum endian = endianess!(mixin(member), e); 99 writeHeader!endian(mixin(member), output); 100 } 101 } else { 102 ubyte[T.sizeof] fieldData; 103 104 static if (e is Endianess.little) { 105 fieldData = nativeToLittleEndian(value); 106 } else { 107 fieldData = nativeToBigEndian(value); 108 } 109 110 put(output, fieldData[]); 111 } 112 } 113 114 @("Able to write headers") 115 unittest { 116 import std.outbuffer; 117 static struct Data { 118 ushort field1; 119 @(Endianess.little) 120 ushort field2; 121 } 122 123 Data d = { field1: 0xDEAD, field2: 0xDEAD }; 124 auto buffer = new OutBuffer(); 125 writeHeader!(Endianess.big)(d, buffer); 126 127 ubyte[] data = [0xDE, 0xAD, 0xAD, 0xDE]; 128 assert(buffer.toBytes() == data); 129 } 130 131 /** 132 * A mixin template that adds by-field casting. 133 * Allows headers to be implemented as templates given a version with minimal effort. 134 */ 135 mixin template Upcast() { 136 T opCast(T)() if (convertable!T) { 137 import std.meta; 138 import std.traits; 139 140 alias This = typeof(this); 141 template hasMember(string name) { 142 enum hasMember = std.traits.hasMember!(This, name); 143 } 144 alias inCommon = Filter!(hasMember, FieldNameTuple!T); 145 // Cast by assigning by member 146 T value; 147 foreach (field; inCommon) { 148 mixin("value."~field~" = cast(typeof(value."~field~"))this."~field~";"); 149 } 150 return value; 151 } 152 }