···4141 ['CDELNRSTZcdelnrstz', 'ČĎĚĽŇŘŠŤŽčďěľňřšťž'], // Caron
4242 ];
43434444+/**
4545+ * Truncates a string to a specified length and adds an ellipsis if needed.
4646+ * @param {string} str - The input string to truncate
4747+ * @param {number} len - The maximum length of the string
4848+ * @returns {string} The truncated string
4949+ */
4450function stringCut(str, len) {
4551 if (str.length > len)
4652 str = str.substring(0, len) + ellipsis;
4753 return str;
4854}
49555656+/**
5757+ * Checks if a string contains only printable characters (ASCII 32-126, plus tab, newline, carriage return)
5858+ * @param {string} s - The string to check
5959+ * @throws {Error} If an unprintable character is found
6060+ */
5061function checkPrintable(s) {
5162 let i, v;
5263 for (i = 0; i < s.length; ++i) {
···5667 }
5768}
58695959-/** Class to manage a stream of bytes, with a zero-copy approach.
6060- * It uses an existing array or binary string and advances a position index. */
7070+/**
7171+ * Class to manage a stream of bytes, with a zero-copy approach.
7272+ * It uses an existing array or binary string and advances a position index.
7373+ */
6174export class Stream {
62756376 /**
7777+ * Creates a new Stream object.
6478 * @param {Stream|array|string} enc data (will not be copied)
6579 * @param {?number} pos starting position (mandatory when `end` is not a Stream)
6680 */
···7488 }
7589 if (typeof this.pos != 'number')
7690 throw new Error('"pos" must be a numeric value');
9191+ // Set up the raw byte access function based on the type of data
7792 if (typeof this.enc == 'string')
7893 this.getRaw = pos => this.enc.charCodeAt(pos);
7994 else if (typeof this.enc[0] == 'number')
···8196 else
8297 throw new Error('"enc" must be a numeric array or a string');
8398 }
8484- /** Get the byte at current position (and increment it) or at a specified position (and avoid moving current position).
8585- * @param {?number} pos read position if specified, else current position (and increment it) */
9999+100100+ /**
101101+ * Get the byte at current position (and increment it) or at a specified position (and avoid moving current position).
102102+ * @param {?number} pos read position if specified, else current position (and increment it)
103103+ * @returns {number} The byte value at the specified position
104104+ */
86105 get(pos) {
87106 if (pos === undefined)
88107 pos = this.pos++;
···90109 throw new Error('Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length);
91110 return this.getRaw(pos);
92111 }
9393- /** Convert a single byte to an hexadcimal string (of length 2).
9494- * @param {number} b */
112112+113113+ /**
114114+ * Convert a single byte to a hexadecimal string (of length 2).
115115+ * @param {number} b - The byte to convert
116116+ * @returns {string} Hexadecimal representation of the byte
117117+ */
95118 static hexByte(b) {
96119 return hexDigits.charAt((b >> 4) & 0xF) + hexDigits.charAt(b & 0xF);
97120 }
9898- /** Hexadecimal dump of a specified region of the stream.
9999- * @param {number} start starting position (included)
100100- * @param {number} end ending position (excluded)
101101- * @param {string} type 'raw', 'byte' or 'dump' (default) */
121121+122122+ /**
123123+ * Hexadecimal dump of a specified region of the stream.
124124+ * @param {number} start - starting position (included)
125125+ * @param {number} end - ending position (excluded)
126126+ * @param {string} type - 'raw', 'byte' or 'dump' (default)
127127+ * @returns {string} Hexadecimal representation of the data
128128+ */
102129 hexDump(start, end, type = 'dump') {
103130 let s = '';
104131 for (let i = start; i < end; ++i) {
···114141 }
115142 return s;
116143 }
117117- /** Base64url dump of a specified region of the stream (according to RFC 4648 section 5).
118118- * @param {number} start starting position (included)
119119- * @param {number} end ending position (excluded)
120120- * @param {string} type 'url' (default, section 5 without padding) or 'std' (section 4 with padding) */
144144+145145+ /**
146146+ * Base64url dump of a specified region of the stream (according to RFC 4648 section 5).
147147+ * @param {number} start - starting position (included)
148148+ * @param {number} end - ending position (excluded)
149149+ * @param {string} type - 'url' (default, section 5 without padding) or 'std' (section 4 with padding)
150150+ * @returns {string} Base64 encoded representation of the data
151151+ */
121152 b64Dump(start, end, type = 'url') {
122153 const b64 = type === 'url' ? b64URL : b64Std,
123154 extra = (end - start) % 3;
···140171 }
141172 return s;
142173 }
174174+175175+ /**
176176+ * Check if a region of the stream contains only ASCII characters (32-176)
177177+ * @param {number} start - starting position (included)
178178+ * @param {number} end - ending position (excluded)
179179+ * @returns {boolean} True if all characters are ASCII, false otherwise
180180+ */
143181 isASCII(start, end) {
144182 for (let i = start; i < end; ++i) {
145183 let c = this.get(i);
···148186 }
149187 return true;
150188 }
189189+190190+ /**
191191+ * Parse a region of the stream as an ISO string
192192+ * @param {number} start - starting position (included)
193193+ * @param {number} end - ending position (excluded)
194194+ * @param {number} maxLength - maximum length of the output string
195195+ * @returns {Object} Object with size and str properties
196196+ */
151197 parseStringISO(start, end, maxLength) {
152198 let s = '';
153199 for (let i = start; i < end; ++i)
154200 s += String.fromCharCode(this.get(i));
155201 return { size: s.length, str: stringCut(s, maxLength) };
156202 }
203203+204204+ /**
205205+ * Parse a region of the stream as a T.61 string
206206+ * @param {number} start - starting position (included)
207207+ * @param {number} end - ending position (excluded)
208208+ * @param {number} maxLength - maximum length of the output string
209209+ * @returns {Object} Object with size and str properties
210210+ */
157211 parseStringT61(start, end, maxLength) {
158212 // warning: this code is not very well tested so far
159213 function merge(c, d) {
···175229 }
176230 return { size: s.length, str: stringCut(s, maxLength) };
177231 }
232232+233233+ /**
234234+ * Parse a region of the stream as a UTF-8 string
235235+ * @param {number} start - starting position (included)
236236+ * @param {number} end - ending position (excluded)
237237+ * @param {number} maxLength - maximum length of the output string
238238+ * @returns {Object} Object with size and str properties
239239+ */
178240 parseStringUTF(start, end, maxLength) {
241241+ /**
242242+ * Helper function to process UTF-8 continuation bytes
243243+ * @param {number} c - The continuation byte
244244+ * @returns {number} The extracted data bits
245245+ */
179246 function ex(c) { // must be 10xxxxxx
180247 if ((c < 0x80) || (c >= 0xC0))
181248 throw new Error('Invalid UTF-8 continuation byte: ' + c);
182249 return (c & 0x3F);
183250 }
251251+ /**
252252+ * Helper function to convert a code point to a surrogate pair
253253+ * @param {number} cp - The code point to convert
254254+ * @returns {string} The surrogate pair as a string
255255+ */
184256 function surrogate(cp) {
185257 if (cp < 0x10000)
186258 throw new Error('UTF-8 overlong encoding, codepoint encoded in 4 bytes: ' + cp);
···206278 }
207279 return { size: s.length, str: stringCut(s, maxLength) };
208280 }
281281+282282+ /**
283283+ * Parse a region of the stream as a BMP (Basic Multilingual Plane) string
284284+ * @param {number} start - starting position (included)
285285+ * @param {number} end - ending position (excluded)
286286+ * @param {number} maxLength - maximum length of the output string
287287+ * @returns {Object} Object with size and str properties
288288+ */
209289 parseStringBMP(start, end, maxLength) {
210290 let s = '', hi, lo;
211291 for (let i = start; i < end; ) {
···215295 }
216296 return { size: s.length, str: stringCut(s, maxLength) };
217297 }
298298+299299+ /**
300300+ * Parse a region of the stream as a time string
301301+ * @param {number} start - starting position (included)
302302+ * @param {number} end - ending position (excluded)
303303+ * @param {boolean} shortYear - Whether to parse as short year (2-digit)
304304+ * @returns {string} Formatted time string
305305+ */
218306 parseTime(start, end, shortYear) {
219307 let s = this.parseStringISO(start, end).str,
220308 m = (shortYear ? reTimeS : reTimeL).exec(s);
···242330 }
243331 return s;
244332 }
333333+334334+ /**
335335+ * Parse a region of the stream as an integer
336336+ * @param {number} start - starting position (included)
337337+ * @param {number} end - ending position (excluded)
338338+ * @returns {string} Formatted integer string
339339+ */
245340 parseInteger(start, end) {
246341 let v = this.get(start),
247342 s = '';
···270365 n = (n << 8n) | BigInt(this.get(i));
271366 return s + n;
272367 }
368368+369369+ /**
370370+ * Parse a region of the stream as a bit string.
371371+ * @param {number} start - starting position (included)
372372+ * @param {number} end - ending position (excluded)
373373+ * @param {number} maxLength - maximum length of the output string
374374+ * @returns {Object} Object with size and str properties
375375+ */
273376 parseBitString(start, end, maxLength) {
274377 const unusedBits = this.get(start);
275378 if (unusedBits > 7)
···286389 }
287390 return { size: lenBit, str: s };
288391 }
392392+393393+ /**
394394+ * Parse a region of the stream as an octet string.
395395+ * @param {number} start - starting position (included)
396396+ * @param {number} end - ending position (excluded)
397397+ * @param {number} maxLength - maximum length of the output string
398398+ * @returns {Object} Object with size and str properties
399399+ */
289400 parseOctetString(start, end, maxLength) {
290290- const len = end - start;
291291- let s;
292401 try {
293293- s = this.parseStringUTF(start, end, maxLength);
402402+ let s = this.parseStringUTF(start, end, maxLength);
294403 checkPrintable(s.str);
295404 return { size: end - start, str: s.str };
296405 } catch (ignore) {
297297- // ignore
406406+ // If UTF-8 parsing fails, fall back to hexadecimal dump
298407 }
408408+ const len = end - start;
299409 maxLength /= 2; // we work in bytes
300410 if (len > maxLength)
301411 end = start + maxLength;
302302- s = '';
412412+ let s = '';
303413 for (let i = start; i < end; ++i)
304414 s += Stream.hexByte(this.get(i));
305415 if (len > maxLength)
306416 s += ellipsis;
307417 return { size: len, str: s };
308418 }
419419+420420+ /**
421421+ * Parse a region of the stream as an OID (Object Identifier).
422422+ * @param {number} start - starting position (included)
423423+ * @param {number} end - ending position (excluded)
424424+ * @param {number} maxLength - maximum length of the output string
425425+ * @param {boolean} isRelative - Whether the OID is relative
426426+ * @returns {string} Formatted OID string
427427+ */
309428 parseOID(start, end, maxLength, isRelative) {
310429 let s = '',
311430 n = 0n,
312431 bits = 0;
313432 for (let i = start; i < end; ++i) {
314433 let v = this.get(i);
434434+ // Shift bits and add the lower 7 bits of the byte
315435 n = (n << 7n) | BigInt(v & 0x7F);
316436 bits += 7;
437437+ // If the most significant bit is 0, this is the last byte of the OID component
317438 if (!(v & 0x80)) { // finished
439439+ // If this is the first component, handle it specially
318440 if (s === '') {
319441 if (isRelative) {
320442 s = n.toString();
···332454 }
333455 if (bits > 0)
334456 s += '.incomplete';
457457+ // If OIDs mapping is available and the OID is absolute, try to resolve it
335458 if (typeof oids === 'object' && !isRelative) {
336459 let oid = oids[s];
337460 if (oid) {
···342465 }
343466 return s;
344467 }
468468+469469+ /**
470470+ * Parse a region of the stream as a relative OID (Object Identifier).
471471+ * @param {number} start - starting position (included)
472472+ * @param {number} end - ending position (excluded)
473473+ * @param {number} maxLength - maximum length of the output string
474474+ * @returns {string} Formatted relative OID string
475475+ */
345476 parseRelativeOID(start, end, maxLength) {
346477 return this.parseOID(start, end, maxLength, true);
347478 }