JavaScript generic ASN.1 parser (mirror)
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add JSDoc comments. (with help form `qwen3-coder:30b`)

+150 -19
+150 -19
asn1.js
··· 41 41 ['CDELNRSTZcdelnrstz', 'ČĎĚĽŇŘŠŤŽčďěľňřšťž'], // Caron 42 42 ]; 43 43 44 + /** 45 + * Truncates a string to a specified length and adds an ellipsis if needed. 46 + * @param {string} str - The input string to truncate 47 + * @param {number} len - The maximum length of the string 48 + * @returns {string} The truncated string 49 + */ 44 50 function stringCut(str, len) { 45 51 if (str.length > len) 46 52 str = str.substring(0, len) + ellipsis; 47 53 return str; 48 54 } 49 55 56 + /** 57 + * Checks if a string contains only printable characters (ASCII 32-126, plus tab, newline, carriage return) 58 + * @param {string} s - The string to check 59 + * @throws {Error} If an unprintable character is found 60 + */ 50 61 function checkPrintable(s) { 51 62 let i, v; 52 63 for (i = 0; i < s.length; ++i) { ··· 56 67 } 57 68 } 58 69 59 - /** Class to manage a stream of bytes, with a zero-copy approach. 60 - * It uses an existing array or binary string and advances a position index. */ 70 + /** 71 + * Class to manage a stream of bytes, with a zero-copy approach. 72 + * It uses an existing array or binary string and advances a position index. 73 + */ 61 74 export class Stream { 62 75 63 76 /** 77 + * Creates a new Stream object. 64 78 * @param {Stream|array|string} enc data (will not be copied) 65 79 * @param {?number} pos starting position (mandatory when `end` is not a Stream) 66 80 */ ··· 74 88 } 75 89 if (typeof this.pos != 'number') 76 90 throw new Error('"pos" must be a numeric value'); 91 + // Set up the raw byte access function based on the type of data 77 92 if (typeof this.enc == 'string') 78 93 this.getRaw = pos => this.enc.charCodeAt(pos); 79 94 else if (typeof this.enc[0] == 'number') ··· 81 96 else 82 97 throw new Error('"enc" must be a numeric array or a string'); 83 98 } 84 - /** Get the byte at current position (and increment it) or at a specified position (and avoid moving current position). 85 - * @param {?number} pos read position if specified, else current position (and increment it) */ 99 + 100 + /** 101 + * Get the byte at current position (and increment it) or at a specified position (and avoid moving current position). 102 + * @param {?number} pos read position if specified, else current position (and increment it) 103 + * @returns {number} The byte value at the specified position 104 + */ 86 105 get(pos) { 87 106 if (pos === undefined) 88 107 pos = this.pos++; ··· 90 109 throw new Error('Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length); 91 110 return this.getRaw(pos); 92 111 } 93 - /** Convert a single byte to an hexadcimal string (of length 2). 94 - * @param {number} b */ 112 + 113 + /** 114 + * Convert a single byte to a hexadecimal string (of length 2). 115 + * @param {number} b - The byte to convert 116 + * @returns {string} Hexadecimal representation of the byte 117 + */ 95 118 static hexByte(b) { 96 119 return hexDigits.charAt((b >> 4) & 0xF) + hexDigits.charAt(b & 0xF); 97 120 } 98 - /** Hexadecimal dump of a specified region of the stream. 99 - * @param {number} start starting position (included) 100 - * @param {number} end ending position (excluded) 101 - * @param {string} type 'raw', 'byte' or 'dump' (default) */ 121 + 122 + /** 123 + * Hexadecimal dump of a specified region of the stream. 124 + * @param {number} start - starting position (included) 125 + * @param {number} end - ending position (excluded) 126 + * @param {string} type - 'raw', 'byte' or 'dump' (default) 127 + * @returns {string} Hexadecimal representation of the data 128 + */ 102 129 hexDump(start, end, type = 'dump') { 103 130 let s = ''; 104 131 for (let i = start; i < end; ++i) { ··· 114 141 } 115 142 return s; 116 143 } 117 - /** Base64url dump of a specified region of the stream (according to RFC 4648 section 5). 118 - * @param {number} start starting position (included) 119 - * @param {number} end ending position (excluded) 120 - * @param {string} type 'url' (default, section 5 without padding) or 'std' (section 4 with padding) */ 144 + 145 + /** 146 + * Base64url dump of a specified region of the stream (according to RFC 4648 section 5). 147 + * @param {number} start - starting position (included) 148 + * @param {number} end - ending position (excluded) 149 + * @param {string} type - 'url' (default, section 5 without padding) or 'std' (section 4 with padding) 150 + * @returns {string} Base64 encoded representation of the data 151 + */ 121 152 b64Dump(start, end, type = 'url') { 122 153 const b64 = type === 'url' ? b64URL : b64Std, 123 154 extra = (end - start) % 3; ··· 140 171 } 141 172 return s; 142 173 } 174 + 175 + /** 176 + * Check if a region of the stream contains only ASCII characters (32-176) 177 + * @param {number} start - starting position (included) 178 + * @param {number} end - ending position (excluded) 179 + * @returns {boolean} True if all characters are ASCII, false otherwise 180 + */ 143 181 isASCII(start, end) { 144 182 for (let i = start; i < end; ++i) { 145 183 let c = this.get(i); ··· 148 186 } 149 187 return true; 150 188 } 189 + 190 + /** 191 + * Parse a region of the stream as an ISO string 192 + * @param {number} start - starting position (included) 193 + * @param {number} end - ending position (excluded) 194 + * @param {number} maxLength - maximum length of the output string 195 + * @returns {Object} Object with size and str properties 196 + */ 151 197 parseStringISO(start, end, maxLength) { 152 198 let s = ''; 153 199 for (let i = start; i < end; ++i) 154 200 s += String.fromCharCode(this.get(i)); 155 201 return { size: s.length, str: stringCut(s, maxLength) }; 156 202 } 203 + 204 + /** 205 + * Parse a region of the stream as a T.61 string 206 + * @param {number} start - starting position (included) 207 + * @param {number} end - ending position (excluded) 208 + * @param {number} maxLength - maximum length of the output string 209 + * @returns {Object} Object with size and str properties 210 + */ 157 211 parseStringT61(start, end, maxLength) { 158 212 // warning: this code is not very well tested so far 159 213 function merge(c, d) { ··· 175 229 } 176 230 return { size: s.length, str: stringCut(s, maxLength) }; 177 231 } 232 + 233 + /** 234 + * Parse a region of the stream as a UTF-8 string 235 + * @param {number} start - starting position (included) 236 + * @param {number} end - ending position (excluded) 237 + * @param {number} maxLength - maximum length of the output string 238 + * @returns {Object} Object with size and str properties 239 + */ 178 240 parseStringUTF(start, end, maxLength) { 241 + /** 242 + * Helper function to process UTF-8 continuation bytes 243 + * @param {number} c - The continuation byte 244 + * @returns {number} The extracted data bits 245 + */ 179 246 function ex(c) { // must be 10xxxxxx 180 247 if ((c < 0x80) || (c >= 0xC0)) 181 248 throw new Error('Invalid UTF-8 continuation byte: ' + c); 182 249 return (c & 0x3F); 183 250 } 251 + /** 252 + * Helper function to convert a code point to a surrogate pair 253 + * @param {number} cp - The code point to convert 254 + * @returns {string} The surrogate pair as a string 255 + */ 184 256 function surrogate(cp) { 185 257 if (cp < 0x10000) 186 258 throw new Error('UTF-8 overlong encoding, codepoint encoded in 4 bytes: ' + cp); ··· 206 278 } 207 279 return { size: s.length, str: stringCut(s, maxLength) }; 208 280 } 281 + 282 + /** 283 + * Parse a region of the stream as a BMP (Basic Multilingual Plane) string 284 + * @param {number} start - starting position (included) 285 + * @param {number} end - ending position (excluded) 286 + * @param {number} maxLength - maximum length of the output string 287 + * @returns {Object} Object with size and str properties 288 + */ 209 289 parseStringBMP(start, end, maxLength) { 210 290 let s = '', hi, lo; 211 291 for (let i = start; i < end; ) { ··· 215 295 } 216 296 return { size: s.length, str: stringCut(s, maxLength) }; 217 297 } 298 + 299 + /** 300 + * Parse a region of the stream as a time string 301 + * @param {number} start - starting position (included) 302 + * @param {number} end - ending position (excluded) 303 + * @param {boolean} shortYear - Whether to parse as short year (2-digit) 304 + * @returns {string} Formatted time string 305 + */ 218 306 parseTime(start, end, shortYear) { 219 307 let s = this.parseStringISO(start, end).str, 220 308 m = (shortYear ? reTimeS : reTimeL).exec(s); ··· 242 330 } 243 331 return s; 244 332 } 333 + 334 + /** 335 + * Parse a region of the stream as an integer 336 + * @param {number} start - starting position (included) 337 + * @param {number} end - ending position (excluded) 338 + * @returns {string} Formatted integer string 339 + */ 245 340 parseInteger(start, end) { 246 341 let v = this.get(start), 247 342 s = ''; ··· 270 365 n = (n << 8n) | BigInt(this.get(i)); 271 366 return s + n; 272 367 } 368 + 369 + /** 370 + * Parse a region of the stream as a bit string. 371 + * @param {number} start - starting position (included) 372 + * @param {number} end - ending position (excluded) 373 + * @param {number} maxLength - maximum length of the output string 374 + * @returns {Object} Object with size and str properties 375 + */ 273 376 parseBitString(start, end, maxLength) { 274 377 const unusedBits = this.get(start); 275 378 if (unusedBits > 7) ··· 286 389 } 287 390 return { size: lenBit, str: s }; 288 391 } 392 + 393 + /** 394 + * Parse a region of the stream as an octet string. 395 + * @param {number} start - starting position (included) 396 + * @param {number} end - ending position (excluded) 397 + * @param {number} maxLength - maximum length of the output string 398 + * @returns {Object} Object with size and str properties 399 + */ 289 400 parseOctetString(start, end, maxLength) { 290 - const len = end - start; 291 - let s; 292 401 try { 293 - s = this.parseStringUTF(start, end, maxLength); 402 + let s = this.parseStringUTF(start, end, maxLength); 294 403 checkPrintable(s.str); 295 404 return { size: end - start, str: s.str }; 296 405 } catch (ignore) { 297 - // ignore 406 + // If UTF-8 parsing fails, fall back to hexadecimal dump 298 407 } 408 + const len = end - start; 299 409 maxLength /= 2; // we work in bytes 300 410 if (len > maxLength) 301 411 end = start + maxLength; 302 - s = ''; 412 + let s = ''; 303 413 for (let i = start; i < end; ++i) 304 414 s += Stream.hexByte(this.get(i)); 305 415 if (len > maxLength) 306 416 s += ellipsis; 307 417 return { size: len, str: s }; 308 418 } 419 + 420 + /** 421 + * Parse a region of the stream as an OID (Object Identifier). 422 + * @param {number} start - starting position (included) 423 + * @param {number} end - ending position (excluded) 424 + * @param {number} maxLength - maximum length of the output string 425 + * @param {boolean} isRelative - Whether the OID is relative 426 + * @returns {string} Formatted OID string 427 + */ 309 428 parseOID(start, end, maxLength, isRelative) { 310 429 let s = '', 311 430 n = 0n, 312 431 bits = 0; 313 432 for (let i = start; i < end; ++i) { 314 433 let v = this.get(i); 434 + // Shift bits and add the lower 7 bits of the byte 315 435 n = (n << 7n) | BigInt(v & 0x7F); 316 436 bits += 7; 437 + // If the most significant bit is 0, this is the last byte of the OID component 317 438 if (!(v & 0x80)) { // finished 439 + // If this is the first component, handle it specially 318 440 if (s === '') { 319 441 if (isRelative) { 320 442 s = n.toString(); ··· 332 454 } 333 455 if (bits > 0) 334 456 s += '.incomplete'; 457 + // If OIDs mapping is available and the OID is absolute, try to resolve it 335 458 if (typeof oids === 'object' && !isRelative) { 336 459 let oid = oids[s]; 337 460 if (oid) { ··· 342 465 } 343 466 return s; 344 467 } 468 + 469 + /** 470 + * Parse a region of the stream as a relative OID (Object Identifier). 471 + * @param {number} start - starting position (included) 472 + * @param {number} end - ending position (excluded) 473 + * @param {number} maxLength - maximum length of the output string 474 + * @returns {string} Formatted relative OID string 475 + */ 345 476 parseRelativeOID(start, end, maxLength) { 346 477 return this.parseOID(start, end, maxLength, true); 347 478 }