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 }