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