1 module tools.encoder_mixin;
2 
3 mixin template Encoder(Element)
4 {
5     import asn1;
6     import std.algorithm: map;
7     import std.array : array, split;
8     import std.conv : ConvException, ConvOverflowException, parse, to;
9     import std.datetime.date : DateTime, DateTimeException;
10     import std.range: chunks;
11     import std.stdio : stderr, stdout;
12     import std.string : indexOf;
13     import std.utf : UTFException;
14 
15     class ArgumentException : Exception
16     {
17         private import std.exception : basicExceptionCtors;
18         mixin basicExceptionCtors;
19     }
20 
21     void encodeBoolean (Element element, string literal)
22     {
23         switch (literal)
24         {
25             case "TRUE" : element.boolean = true; break;
26             case "FALSE" : element.boolean = false; break;
27             default: stderr.rawWrite("Invalid boolean. Valid options (case insensitive): TRUE, FALSE.\n");
28         }
29     }
30 
31     void encodeInteger (Element element, string literal)
32     {
33         try
34         {
35             element.integer!ptrdiff_t = literal.to!ptrdiff_t;
36         }
37         catch (ConvException e)
38         {
39             stderr.rawWrite(e.msg);
40         }
41     }
42 
43     void encodeBitString (Element element, string literal)
44     {
45         bool[] bits;
46         bits.length = literal.length;
47         for (size_t i = 0u; i < bits.length; i++)
48         {
49             if (literal[i] == '1') bits[i] = true;
50             else if (literal[i] == '0') bits[i] = false;
51             else
52             {
53                 stderr.rawWrite("Invalid BIT STRING. BIT STRING only accepts 1s and 0s.\n");
54                 return;
55             }
56         }
57         element.bitString = bits;
58     }
59 
60     void encodeOctetString (Element element, string literal)
61     {
62         // https://stackoverflow.com/questions/23725222/how-do-i-convert-a-bigint-to-a-ubyte/23741556#23741556
63         // https://forum.dlang.org/post/welnhsfiqyhchagxilet@forum.dlang.org
64         if (literal.length % 2u)
65         {
66             stderr.rawWrite("Cannot decode an odd number of hexadecimal characters.\n");
67             return;
68         }
69         element.octetString = literal
70             .chunks(2)
71             .map!(twoDigits => twoDigits.parse!ubyte(16))
72             .array();
73     }
74 
75     alias encodeOID = encodeObjectIdentifier;
76     alias encodeObjectID = encodeObjectIdentifier;
77     void encodeObjectIdentifier (Element element, string literal)
78     {
79         try
80         {
81             element.objectIdentifier = new ObjectIdentifier(literal);
82         }
83         catch (OIDException e)
84         {
85             stderr.rawWrite(e.msg);
86         }
87     }
88 
89     alias encodeOD = encodeObjectDescriptor;
90     void encodeObjectDescriptor (Element element, string literal)
91     {
92         try
93         {
94             element.objectDescriptor = literal;
95         }
96         catch (ASN1ValueException e)
97         {
98             stderr.rawWrite(e.msg ~ "\n");
99         }
100     }
101 
102     void encodeReal (Element element, string literal)
103     {
104         try
105         {
106             element.realNumber!real = literal.to!real;
107         }
108         catch (ConvException e)
109         {
110             stderr.rawWrite(e.msg);
111         }
112     }
113 
114     void encodeEnumerated (Element element, string literal)
115     {
116         try
117         {
118             element.enumerated!long = literal.to!long;
119         }
120         catch (ConvException e)
121         {
122             stderr.rawWrite(e.msg);
123         }
124     }
125 
126     alias encodeUTF8String = encodeUnicodeTransformationFormat8String;
127     void encodeUnicodeTransformationFormat8String (Element element, string literal)
128     {
129         try
130         {
131             element.utf8String = literal;
132         }
133         catch (UTFException e)
134         {
135             stderr.rawWrite(e.msg ~ "\n");
136         }
137     }
138 
139     alias encodeROID = encodeRelativeObjectIdentifier;
140     alias encodeRelativeOID = encodeRelativeObjectIdentifier;
141     alias encodeRelativeObjectID = encodeRelativeObjectIdentifier;
142     void encodeRelativeObjectIdentifier (Element element, string literal)
143     {
144         import std.array : split;
145         string[] segments = literal.split(".");
146         uint[] numbers;
147         numbers.length = segments.length;
148         for (size_t i = 0u; i < segments.length; i++)
149         {
150             numbers[i] = segments[i].to!uint;
151         }
152 
153         Appender!(OIDNode[]) nodes = appender!(OIDNode[]);
154         foreach (number; numbers)
155         {
156             nodes.put(OIDNode(number));
157         }
158 
159         element.relativeObjectIdentifier = nodes.data;
160     }
161 
162     void encodeNumericString (Element element, string literal)
163     {
164         try
165         {
166             element.numericString = literal;
167         }
168         catch (ASN1ValueException e)
169         {
170             stderr.rawWrite(e.msg ~ "\n");
171         }
172     }
173 
174     void encodePrintableString (Element element, string literal)
175     {
176         try
177         {
178             element.printableString = literal;
179         }
180         catch (ASN1ValueException e)
181         {
182             stderr.rawWrite(e.msg ~ "\n");
183         }
184     }
185 
186     alias encodeT61String = encodeTeletexString;
187     void encodeTeletexString (Element element, string literal)
188     {
189         // https://stackoverflow.com/questions/23725222/how-do-i-convert-a-bigint-to-a-ubyte/23741556#23741556
190         // https://forum.dlang.org/post/welnhsfiqyhchagxilet@forum.dlang.org
191         if (literal.length % 2u)
192         {
193             stderr.rawWrite("Cannot decode an odd number of hexadecimal characters.\n");
194             return;
195         }
196         element.teletexString = literal
197             .chunks(2)
198             .map!(twoDigits => twoDigits.parse!ubyte(16))
199             .array();
200     }
201 
202     void encodeVideotexString (Element element, string literal)
203     {
204         // https://stackoverflow.com/questions/23725222/how-do-i-convert-a-bigint-to-a-ubyte/23741556#23741556
205         // https://forum.dlang.org/post/welnhsfiqyhchagxilet@forum.dlang.org
206         if (literal.length % 2u)
207         {
208             stderr.rawWrite("Cannot decode an odd number of hexadecimal characters.\n");
209             return;
210         }
211         element.videotexString = literal
212             .chunks(2)
213             .map!(twoDigits => twoDigits.parse!ubyte(16))
214             .array();
215     }
216 
217     alias encodeIA5String = encodeInternationalAlphabet5String;
218     void encodeInternationalAlphabet5String (Element element, string literal)
219     {
220         try
221         {
222             element.ia5String = literal;
223         }
224         catch (ASN1ValueException e)
225         {
226             stderr.rawWrite(e.msg ~ "\n");
227         }
228     }
229 
230     alias encodeUTCTime = encodeCoordinatedUniversalTime;
231     void encodeCoordinatedUniversalTime (Element element, string literal)
232     {
233         try
234         {
235             element.utcTime = DateTime.fromISOString(literal);
236         }
237         catch (DateTimeException e)
238         {
239             stderr.rawWrite(e.msg ~ "\n");
240         }
241     }
242 
243     void encodeGeneralizedTime (Element element, string literal)
244     {
245         try
246         {
247             element.generalizedTime = DateTime.fromISOString(literal);
248         }
249         catch (DateTimeException e)
250         {
251             stderr.rawWrite(e.msg ~ "\n");
252         }
253     }
254 
255     void encodeGraphicString (Element element, string literal)
256     {
257         try
258         {
259             element.graphicString = literal;
260         }
261         catch (ASN1ValueException e)
262         {
263             stderr.rawWrite(e.msg ~ "\n");
264         }
265     }
266 
267     void encodeVisibleString (Element element, string literal)
268     {
269         try
270         {
271             element.visibleString = literal;
272         }
273         catch (ASN1ValueException e)
274         {
275             stderr.rawWrite(e.msg ~ "\n");
276         }
277     }
278 
279     void encodeGeneralString (Element element, string literal)
280     {
281         try
282         {
283             element.generalString = literal;
284         }
285         catch (ASN1ValueException e)
286         {
287             stderr.rawWrite(e.msg ~ "\n");
288         }
289     }
290 
291     void encodeUniversalString (Element element, string literal)
292     {
293         try
294         {
295             element.universalString = cast(dstring) literal;
296         }
297         catch (ASN1ValueException e)
298         {
299             stderr.rawWrite(e.msg ~ "\n");
300         }
301     }
302 
303     alias encodeBMPString = encodeBasicMultilingualPlaneString;
304     void encodeBasicMultilingualPlaneString (Element element, string literal)
305     {
306         try
307         {
308             element.bmpString = cast(wstring) literal;
309         }
310         catch (ASN1ValueException e)
311         {
312             stderr.rawWrite(e.msg ~ "\n");
313         }
314     }
315 
316     ubyte[] encode (string arg)
317     {
318         if (arg.length < 11u)
319         {
320             stderr.rawWrite("Argument too short.\n");
321             return [];
322         }
323 
324         if (arg[0] != '[')
325         {
326             stderr.rawWrite("Each argument must start with a '['.\n");
327             return [];
328         }
329 
330         ptrdiff_t indexOfDefinition = arg.indexOf("]::=");
331 
332         if (indexOfDefinition == -1)
333         {
334             stderr.rawWrite("Each argument must be of the form [??#]::=???:...\n");
335             return [];
336         }
337 
338         Element element = new Element();
339         switch (arg[1])
340         {
341             case ('U'): element.tagClass = ASN1TagClass.universal; break;
342             case ('A'): element.tagClass = ASN1TagClass.application; break;
343             case ('C'): element.tagClass = ASN1TagClass.contextSpecific; break;
344             case ('P'): element.tagClass = ASN1TagClass.privatelyDefined; break;
345             default: stderr.rawWrite("Invalid tag class selection. Must be U, A, C, or P.\n");
346         }
347         switch (arg[2])
348         {
349             case ('P'): element.construction = ASN1Construction.primitive; break;
350             case ('C'): element.construction = ASN1Construction.constructed; break;
351             default: stderr.rawWrite("Invalid construction selection. Must be P or C.\n");
352         }
353 
354         {
355             string number = arg[3 .. indexOfDefinition];
356             element.tagNumber = number.to!size_t;
357         }
358 
359         {
360             string valueVector = arg[(indexOfDefinition + 4u) .. $];
361             ptrdiff_t indexOfColon = valueVector.indexOf(":");
362             if (indexOfColon == -1)
363             {
364                 stderr.rawWrite("Invalid value. Must provide a encoding method.\n");
365                 return [];
366             }
367 
368             switch (valueVector[0 .. indexOfColon])
369             {
370                 case("eoc"): break;
371                 case("bool"): encodeBoolean(element, valueVector[(indexOfColon + 1u) .. $]); break;
372                 case("int"): encodeInteger(element, valueVector[(indexOfColon + 1u) .. $]); break;
373                 case("bit"): encodeBitString(element, valueVector[(indexOfColon + 1u) .. $]); break;
374                 case("oct"): encodeOctetString(element, valueVector[(indexOfColon + 1u) .. $]); break;
375                 case("null"): break;
376                 case("oid"): encodeOID(element, valueVector[(indexOfColon + 1u) .. $]); break;
377                 case("od"): encodeObjectDescriptor(element, valueVector[(indexOfColon + 1u) .. $]); break;
378                 case("real"): encodeReal(element, valueVector[(indexOfColon + 1u) .. $]); break;
379                 case("enum"): encodeEnumerated(element, valueVector[(indexOfColon + 1u) .. $]); break;
380                 case("utf8"): encodeUTF8String(element, valueVector[(indexOfColon + 1u) .. $]); break;
381                 case("roid"): encodeRelativeOID(element, valueVector[(indexOfColon + 1u) .. $]); break;
382                 case("numeric"): encodeNumericString(element, valueVector[(indexOfColon + 1u) .. $]); break;
383                 case("printable"): encodePrintableString(element, valueVector[(indexOfColon + 1u) .. $]); break;
384                 case("teletex"): encodeTeletexString(element, valueVector[(indexOfColon + 1u) .. $]); break;
385                 case("videotex"): encodeVideotexString(element, valueVector[(indexOfColon + 1u) .. $]); break;
386                 case("ia5"): encodeIA5String(element, valueVector[(indexOfColon + 1u) .. $]); break;
387                 case("utc"): encodeUTCTime(element, valueVector[(indexOfColon + 1u) .. $]); break;
388                 case("time"): encodeGeneralizedTime(element, valueVector[(indexOfColon + 1u) .. $]); break;
389                 case("graphic"): encodeGraphicString(element, valueVector[(indexOfColon + 1u) .. $]); break;
390                 case("visible"): encodeVisibleString(element, valueVector[(indexOfColon + 1u) .. $]); break;
391                 case("general"): encodeGeneralString(element, valueVector[(indexOfColon + 1u) .. $]); break;
392                 case("universal"): encodeUniversalString(element, valueVector[(indexOfColon + 1u) .. $]); break;
393                 case("bmp"): encodeBMPString(element, valueVector[(indexOfColon + 1u) .. $]); break;
394                 default: stderr.rawWrite("Invalid encoding method: '" ~ valueVector[0 .. indexOfColon] ~ "' \n");
395             }
396         }
397 
398         return element.toBytes;
399     }
400 
401     int main(string[] args)
402     {
403         ubyte[] encodedData;
404 
405         if (args.length < 2u)
406         {
407             stderr.rawWrite("Too few arguments.\n");
408             return 1;
409         }
410 
411         foreach (arg; args[1 .. $])
412         {
413             encodedData ~= encode(arg);
414         }
415 
416         stdout.rawWrite(encodedData);
417         return 0;
418     }
419 }