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 }