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 }