/src/openssl30/crypto/asn1/asn1_parse.c
| Line | Count | Source (jump to first uncovered line) | 
| 1 |  | /* | 
| 2 |  |  * Copyright 1995-2023 The OpenSSL Project Authors. All Rights Reserved. | 
| 3 |  |  * | 
| 4 |  |  * Licensed under the Apache License 2.0 (the "License").  You may not use | 
| 5 |  |  * this file except in compliance with the License.  You can obtain a copy | 
| 6 |  |  * in the file LICENSE in the source distribution or at | 
| 7 |  |  * https://www.openssl.org/source/license.html | 
| 8 |  |  */ | 
| 9 |  |  | 
| 10 |  | #include <stdio.h> | 
| 11 |  | #include "internal/cryptlib.h" | 
| 12 |  | #include <openssl/buffer.h> | 
| 13 |  | #include <openssl/objects.h> | 
| 14 |  | #include <openssl/asn1.h> | 
| 15 |  |  | 
| 16 |  | #ifndef ASN1_PARSE_MAXDEPTH | 
| 17 | 3.81M | #define ASN1_PARSE_MAXDEPTH 128 | 
| 18 |  | #endif | 
| 19 |  |  | 
| 20 |  | static int asn1_parse2(BIO *bp, const unsigned char **pp, long length, | 
| 21 |  |                        int offset, int depth, int indent, int dump); | 
| 22 |  | static int asn1_print_info(BIO *bp, long offset, int depth, int hl, long len, | 
| 23 |  |                            int tag, int xclass, int constructed, int indent) | 
| 24 | 14.7M | { | 
| 25 | 14.7M |     char str[128]; | 
| 26 | 14.7M |     const char *p; | 
| 27 | 14.7M |     int pop_f_prefix = 0; | 
| 28 | 14.7M |     long saved_indent = -1; | 
| 29 | 14.7M |     int i = 0; | 
| 30 | 14.7M |     BIO *bio = NULL; | 
| 31 |  |  | 
| 32 | 14.7M |     if (constructed & V_ASN1_CONSTRUCTED) | 
| 33 | 7.32M |         p = "cons: "; | 
| 34 | 7.39M |     else | 
| 35 | 7.39M |         p = "prim: "; | 
| 36 | 14.7M |     if (constructed != (V_ASN1_CONSTRUCTED | 1)) { | 
| 37 | 12.7M |         if (BIO_snprintf(str, sizeof(str), "%5ld:d=%-2d hl=%ld l=%4ld %s", | 
| 38 | 12.7M |                          offset, depth, (long)hl, len, p) <= 0) | 
| 39 | 0 |             goto err; | 
| 40 | 12.7M |     } else { | 
| 41 | 1.97M |         if (BIO_snprintf(str, sizeof(str), "%5ld:d=%-2d hl=%ld l=inf  %s", | 
| 42 | 1.97M |                          offset, depth, (long)hl, p) <= 0) | 
| 43 | 0 |             goto err; | 
| 44 | 1.97M |     } | 
| 45 | 14.7M |     if (bp != NULL) { | 
| 46 | 14.7M |         if (BIO_set_prefix(bp, str) <= 0) { | 
| 47 | 14.7M |             if ((bio = BIO_new(BIO_f_prefix())) == NULL | 
| 48 | 14.7M |                     || (bp = BIO_push(bio, bp)) == NULL) | 
| 49 | 0 |                 goto err; | 
| 50 | 14.7M |             pop_f_prefix = 1; | 
| 51 | 14.7M |         } | 
| 52 | 14.7M |         saved_indent = BIO_get_indent(bp); | 
| 53 | 14.7M |         if (BIO_set_prefix(bp, str) <= 0 || BIO_set_indent(bp, indent) <= 0) | 
| 54 | 0 |             goto err; | 
| 55 | 14.7M |     } | 
| 56 |  |  | 
| 57 |  |     /* | 
| 58 |  |      * BIO_set_prefix made a copy of |str|, so we can safely use it for | 
| 59 |  |      * something else, ASN.1 tag printout. | 
| 60 |  |      */ | 
| 61 | 14.7M |     p = str; | 
| 62 | 14.7M |     if ((xclass & V_ASN1_PRIVATE) == V_ASN1_PRIVATE) | 
| 63 | 11.8k |         BIO_snprintf(str, sizeof(str), "priv [ %d ] ", tag); | 
| 64 | 14.7M |     else if ((xclass & V_ASN1_CONTEXT_SPECIFIC) == V_ASN1_CONTEXT_SPECIFIC) | 
| 65 | 445k |         BIO_snprintf(str, sizeof(str), "cont [ %d ]", tag); | 
| 66 | 14.2M |     else if ((xclass & V_ASN1_APPLICATION) == V_ASN1_APPLICATION) | 
| 67 | 276k |         BIO_snprintf(str, sizeof(str), "appl [ %d ]", tag); | 
| 68 | 13.9M |     else if (tag > 30) | 
| 69 | 5.44k |         BIO_snprintf(str, sizeof(str), "<ASN1 %d>", tag); | 
| 70 | 13.9M |     else | 
| 71 | 13.9M |         p = ASN1_tag2str(tag); | 
| 72 |  |  | 
| 73 | 14.7M |     i = (BIO_printf(bp, "%-18s", p) > 0); | 
| 74 | 14.7M |  err: | 
| 75 | 14.7M |     if (saved_indent >= 0) | 
| 76 | 14.7M |         BIO_set_indent(bp, saved_indent); | 
| 77 | 14.7M |     if (pop_f_prefix) | 
| 78 | 14.7M |         BIO_pop(bp); | 
| 79 | 14.7M |     BIO_free(bio); | 
| 80 | 14.7M |     return i; | 
| 81 | 14.7M | } | 
| 82 |  |  | 
| 83 |  | int ASN1_parse(BIO *bp, const unsigned char *pp, long len, int indent) | 
| 84 | 0 | { | 
| 85 | 0 |     return asn1_parse2(bp, &pp, len, 0, 0, indent, 0); | 
| 86 | 0 | } | 
| 87 |  |  | 
| 88 |  | int ASN1_parse_dump(BIO *bp, const unsigned char *pp, long len, int indent, | 
| 89 |  |                     int dump) | 
| 90 | 736k | { | 
| 91 | 736k |     return asn1_parse2(bp, &pp, len, 0, 0, indent, dump); | 
| 92 | 736k | } | 
| 93 |  |  | 
| 94 |  | static int asn1_parse2(BIO *bp, const unsigned char **pp, long length, | 
| 95 |  |                        int offset, int depth, int indent, int dump) | 
| 96 | 3.81M | { | 
| 97 | 3.81M |     const unsigned char *p, *ep, *tot, *op, *opp; | 
| 98 | 3.81M |     long len; | 
| 99 | 3.81M |     int tag, xclass, ret = 0; | 
| 100 | 3.81M |     int nl, hl, j, r; | 
| 101 | 3.81M |     ASN1_OBJECT *o = NULL; | 
| 102 | 3.81M |     ASN1_OCTET_STRING *os = NULL; | 
| 103 | 3.81M |     ASN1_INTEGER *ai = NULL; | 
| 104 | 3.81M |     ASN1_ENUMERATED *ae = NULL; | 
| 105 |  |     /* ASN1_BMPSTRING *bmp=NULL; */ | 
| 106 | 3.81M |     int dump_indent, dump_cont = 0; | 
| 107 |  |  | 
| 108 | 3.81M |     if (depth > ASN1_PARSE_MAXDEPTH) { | 
| 109 | 49 |         BIO_puts(bp, "BAD RECURSION DEPTH\n"); | 
| 110 | 49 |         return 0; | 
| 111 | 49 |     } | 
| 112 |  |  | 
| 113 | 3.81M |     dump_indent = 6;            /* Because we know BIO_dump_indent() */ | 
| 114 | 3.81M |     p = *pp; | 
| 115 | 3.81M |     tot = p + length; | 
| 116 | 16.5M |     while (length > 0) { | 
| 117 | 14.7M |         op = p; | 
| 118 | 14.7M |         j = ASN1_get_object(&p, &len, &tag, &xclass, length); | 
| 119 | 14.7M |         if (j & 0x80) { | 
| 120 | 7.38k |             BIO_puts(bp, "Error in encoding\n"); | 
| 121 | 7.38k |             goto end; | 
| 122 | 7.38k |         } | 
| 123 | 14.7M |         hl = (p - op); | 
| 124 | 14.7M |         length -= hl; | 
| 125 |  |         /* | 
| 126 |  |          * if j == 0x21 it is a constructed indefinite length object | 
| 127 |  |          */ | 
| 128 | 14.7M |         if (!asn1_print_info(bp, (long)offset + (long)(op - *pp), depth, | 
| 129 | 14.7M |                              hl, len, tag, xclass, j, (indent) ? depth : 0)) | 
| 130 | 0 |             goto end; | 
| 131 | 14.7M |         if (j & V_ASN1_CONSTRUCTED) { | 
| 132 | 7.32M |             const unsigned char *sp = p; | 
| 133 |  |  | 
| 134 | 7.32M |             ep = p + len; | 
| 135 | 7.32M |             if (BIO_write(bp, "\n", 1) <= 0) | 
| 136 | 0 |                 goto end; | 
| 137 | 7.32M |             if (len > length) { | 
| 138 | 0 |                 BIO_printf(bp, "length is greater than %ld\n", length); | 
| 139 | 0 |                 goto end; | 
| 140 | 0 |             } | 
| 141 | 7.32M |             if ((j == 0x21) && (len == 0)) { | 
| 142 | 1.97M |                 for (;;) { | 
| 143 | 1.97M |                     r = asn1_parse2(bp, &p, (long)(tot - p), | 
| 144 | 1.97M |                                     offset + (p - *pp), depth + 1, | 
| 145 | 1.97M |                                     indent, dump); | 
| 146 | 1.97M |                     if (r == 0) | 
| 147 | 34.8k |                         goto end; | 
| 148 | 1.94M |                     if ((r == 2) || (p >= tot)) { | 
| 149 | 1.94M |                         len = p - sp; | 
| 150 | 1.94M |                         break; | 
| 151 | 1.94M |                     } | 
| 152 | 1.94M |                 } | 
| 153 | 5.35M |             } else { | 
| 154 | 5.35M |                 long tmp = len; | 
| 155 |  |  | 
| 156 | 6.75M |                 while (p < ep) { | 
| 157 | 1.40M |                     sp = p; | 
| 158 | 1.40M |                     r = asn1_parse2(bp, &p, tmp, | 
| 159 | 1.40M |                                     offset + (p - *pp), depth + 1, | 
| 160 | 1.40M |                                     indent, dump); | 
| 161 | 1.40M |                     if (r == 0) | 
| 162 | 8.80k |                         goto end; | 
| 163 | 1.40M |                     tmp -= p - sp; | 
| 164 | 1.40M |                 } | 
| 165 | 5.35M |             } | 
| 166 | 7.39M |         } else if (xclass != 0) { | 
| 167 | 537k |             p += len; | 
| 168 | 537k |             if (BIO_write(bp, "\n", 1) <= 0) | 
| 169 | 0 |                 goto end; | 
| 170 | 6.85M |         } else { | 
| 171 | 6.85M |             nl = 0; | 
| 172 | 6.85M |             if ((tag == V_ASN1_PRINTABLESTRING) || | 
| 173 | 6.85M |                 (tag == V_ASN1_T61STRING) || | 
| 174 | 6.85M |                 (tag == V_ASN1_IA5STRING) || | 
| 175 | 6.85M |                 (tag == V_ASN1_VISIBLESTRING) || | 
| 176 | 6.85M |                 (tag == V_ASN1_NUMERICSTRING) || | 
| 177 | 6.85M |                 (tag == V_ASN1_UTF8STRING) || | 
| 178 | 6.85M |                 (tag == V_ASN1_UTCTIME) || (tag == V_ASN1_GENERALIZEDTIME)) { | 
| 179 | 424k |                 if (BIO_write(bp, ":", 1) <= 0) | 
| 180 | 0 |                     goto end; | 
| 181 | 424k |                 if ((len > 0) && BIO_write(bp, (const char *)p, (int)len) | 
| 182 | 269k |                     != (int)len) | 
| 183 | 0 |                     goto end; | 
| 184 | 6.42M |             } else if (tag == V_ASN1_OBJECT) { | 
| 185 | 1.73M |                 opp = op; | 
| 186 | 1.73M |                 if (d2i_ASN1_OBJECT(&o, &opp, len + hl) != NULL) { | 
| 187 | 1.69M |                     if (BIO_write(bp, ":", 1) <= 0) | 
| 188 | 0 |                         goto end; | 
| 189 | 1.69M |                     i2a_ASN1_OBJECT(bp, o); | 
| 190 | 1.69M |                 } else { | 
| 191 | 40.4k |                     if (BIO_puts(bp, ":BAD OBJECT") <= 0) | 
| 192 | 0 |                         goto end; | 
| 193 | 40.4k |                     dump_cont = 1; | 
| 194 | 40.4k |                 } | 
| 195 | 4.69M |             } else if (tag == V_ASN1_BOOLEAN) { | 
| 196 | 52.1k |                 if (len != 1) { | 
| 197 | 13.4k |                     if (BIO_puts(bp, ":BAD BOOLEAN") <= 0) | 
| 198 | 0 |                         goto end; | 
| 199 | 13.4k |                     dump_cont = 1; | 
| 200 | 13.4k |                 } | 
| 201 | 52.1k |                 if (len > 0) | 
| 202 | 47.6k |                     BIO_printf(bp, ":%u", p[0]); | 
| 203 | 4.64M |             } else if (tag == V_ASN1_BMPSTRING) { | 
| 204 |  |                 /* do the BMP thang */ | 
| 205 | 4.62M |             } else if (tag == V_ASN1_OCTET_STRING) { | 
| 206 | 273k |                 int i, printable = 1; | 
| 207 |  |  | 
| 208 | 273k |                 opp = op; | 
| 209 | 273k |                 os = d2i_ASN1_OCTET_STRING(NULL, &opp, len + hl); | 
| 210 | 273k |                 if (os != NULL && os->length > 0) { | 
| 211 | 226k |                     opp = os->data; | 
| 212 |  |                     /* | 
| 213 |  |                      * testing whether the octet string is printable | 
| 214 |  |                      */ | 
| 215 | 560k |                     for (i = 0; i < os->length; i++) { | 
| 216 | 545k |                         if (((opp[i] < ' ') && | 
| 217 | 545k |                              (opp[i] != '\n') && | 
| 218 | 545k |                              (opp[i] != '\r') && | 
| 219 | 545k |                              (opp[i] != '\t')) || (opp[i] > '~')) { | 
| 220 | 212k |                             printable = 0; | 
| 221 | 212k |                             break; | 
| 222 | 212k |                         } | 
| 223 | 545k |                     } | 
| 224 | 226k |                     if (printable) | 
| 225 |  |                         /* printable string */ | 
| 226 | 14.4k |                     { | 
| 227 | 14.4k |                         if (BIO_write(bp, ":", 1) <= 0) | 
| 228 | 0 |                             goto end; | 
| 229 | 14.4k |                         if (BIO_write(bp, (const char *)opp, os->length) <= 0) | 
| 230 | 0 |                             goto end; | 
| 231 | 212k |                     } else if (!dump) | 
| 232 |  |                         /* | 
| 233 |  |                          * not printable => print octet string as hex dump | 
| 234 |  |                          */ | 
| 235 | 212k |                     { | 
| 236 | 212k |                         if (BIO_write(bp, "[HEX DUMP]:", 11) <= 0) | 
| 237 | 0 |                             goto end; | 
| 238 | 13.5M |                         for (i = 0; i < os->length; i++) { | 
| 239 | 13.3M |                             if (BIO_printf(bp, "%02X", opp[i]) <= 0) | 
| 240 | 0 |                                 goto end; | 
| 241 | 13.3M |                         } | 
| 242 | 212k |                     } else | 
| 243 |  |                         /* print the normal dump */ | 
| 244 | 0 |                     { | 
| 245 | 0 |                         if (!nl) { | 
| 246 | 0 |                             if (BIO_write(bp, "\n", 1) <= 0) | 
| 247 | 0 |                                 goto end; | 
| 248 | 0 |                         } | 
| 249 | 0 |                         if (BIO_dump_indent(bp, | 
| 250 | 0 |                                             (const char *)opp, | 
| 251 | 0 |                                             ((dump == -1 || dump > | 
| 252 | 0 |                                               os-> | 
| 253 | 0 |                                               length) ? os->length : dump), | 
| 254 | 0 |                                             dump_indent) <= 0) | 
| 255 | 0 |                             goto end; | 
| 256 | 0 |                         nl = 1; | 
| 257 | 0 |                     } | 
| 258 | 226k |                 } | 
| 259 | 273k |                 ASN1_OCTET_STRING_free(os); | 
| 260 | 273k |                 os = NULL; | 
| 261 | 4.34M |             } else if (tag == V_ASN1_INTEGER) { | 
| 262 | 690k |                 int i; | 
| 263 |  |  | 
| 264 | 690k |                 opp = op; | 
| 265 | 690k |                 ai = d2i_ASN1_INTEGER(NULL, &opp, len + hl); | 
| 266 | 690k |                 if (ai != NULL) { | 
| 267 | 631k |                     if (BIO_write(bp, ":", 1) <= 0) | 
| 268 | 0 |                         goto end; | 
| 269 | 631k |                     if (ai->type == V_ASN1_NEG_INTEGER) | 
| 270 | 129k |                         if (BIO_write(bp, "-", 1) <= 0) | 
| 271 | 0 |                             goto end; | 
| 272 | 13.4M |                     for (i = 0; i < ai->length; i++) { | 
| 273 | 12.8M |                         if (BIO_printf(bp, "%02X", ai->data[i]) <= 0) | 
| 274 | 0 |                             goto end; | 
| 275 | 12.8M |                     } | 
| 276 | 631k |                     if (ai->length == 0) { | 
| 277 | 0 |                         if (BIO_write(bp, "00", 2) <= 0) | 
| 278 | 0 |                             goto end; | 
| 279 | 0 |                     } | 
| 280 | 631k |                 } else { | 
| 281 | 59.8k |                     if (BIO_puts(bp, ":BAD INTEGER") <= 0) | 
| 282 | 0 |                         goto end; | 
| 283 | 59.8k |                     dump_cont = 1; | 
| 284 | 59.8k |                 } | 
| 285 | 690k |                 ASN1_INTEGER_free(ai); | 
| 286 | 690k |                 ai = NULL; | 
| 287 | 3.65M |             } else if (tag == V_ASN1_ENUMERATED) { | 
| 288 | 31.4k |                 int i; | 
| 289 |  |  | 
| 290 | 31.4k |                 opp = op; | 
| 291 | 31.4k |                 ae = d2i_ASN1_ENUMERATED(NULL, &opp, len + hl); | 
| 292 | 31.4k |                 if (ae != NULL) { | 
| 293 | 24.1k |                     if (BIO_write(bp, ":", 1) <= 0) | 
| 294 | 0 |                         goto end; | 
| 295 | 24.1k |                     if (ae->type == V_ASN1_NEG_ENUMERATED) | 
| 296 | 7.89k |                         if (BIO_write(bp, "-", 1) <= 0) | 
| 297 | 0 |                             goto end; | 
| 298 | 7.41M |                     for (i = 0; i < ae->length; i++) { | 
| 299 | 7.39M |                         if (BIO_printf(bp, "%02X", ae->data[i]) <= 0) | 
| 300 | 0 |                             goto end; | 
| 301 | 7.39M |                     } | 
| 302 | 24.1k |                     if (ae->length == 0) { | 
| 303 | 0 |                         if (BIO_write(bp, "00", 2) <= 0) | 
| 304 | 0 |                             goto end; | 
| 305 | 0 |                     } | 
| 306 | 24.1k |                 } else { | 
| 307 | 7.27k |                     if (BIO_puts(bp, ":BAD ENUMERATED") <= 0) | 
| 308 | 0 |                         goto end; | 
| 309 | 7.27k |                     dump_cont = 1; | 
| 310 | 7.27k |                 } | 
| 311 | 31.4k |                 ASN1_ENUMERATED_free(ae); | 
| 312 | 31.4k |                 ae = NULL; | 
| 313 | 3.62M |             } else if (len > 0 && dump) { | 
| 314 | 0 |                 if (!nl) { | 
| 315 | 0 |                     if (BIO_write(bp, "\n", 1) <= 0) | 
| 316 | 0 |                         goto end; | 
| 317 | 0 |                 } | 
| 318 | 0 |                 if (BIO_dump_indent(bp, (const char *)p, | 
| 319 | 0 |                                     ((dump == -1 || dump > len) ? len : dump), | 
| 320 | 0 |                                     dump_indent) <= 0) | 
| 321 | 0 |                     goto end; | 
| 322 | 0 |                 nl = 1; | 
| 323 | 0 |             } | 
| 324 | 6.85M |             if (dump_cont) { | 
| 325 | 121k |                 int i; | 
| 326 | 121k |                 const unsigned char *tmp = op + hl; | 
| 327 | 121k |                 if (BIO_puts(bp, ":[") <= 0) | 
| 328 | 0 |                     goto end; | 
| 329 | 39.2M |                 for (i = 0; i < len; i++) { | 
| 330 | 39.0M |                     if (BIO_printf(bp, "%02X", tmp[i]) <= 0) | 
| 331 | 0 |                         goto end; | 
| 332 | 39.0M |                 } | 
| 333 | 121k |                 if (BIO_puts(bp, "]") <= 0) | 
| 334 | 0 |                     goto end; | 
| 335 | 121k |                 dump_cont = 0; | 
| 336 | 121k |             } | 
| 337 |  |  | 
| 338 | 6.85M |             if (!nl) { | 
| 339 | 6.85M |                 if (BIO_write(bp, "\n", 1) <= 0) | 
| 340 | 0 |                     goto end; | 
| 341 | 6.85M |             } | 
| 342 | 6.85M |             p += len; | 
| 343 | 6.85M |             if ((tag == V_ASN1_EOC) && (xclass == 0)) { | 
| 344 | 1.94M |                 ret = 2;        /* End of sequence */ | 
| 345 | 1.94M |                 goto end; | 
| 346 | 1.94M |             } | 
| 347 | 6.85M |         } | 
| 348 | 12.7M |         length -= len; | 
| 349 | 12.7M |     } | 
| 350 | 1.82M |     ret = 1; | 
| 351 | 3.81M |  end: | 
| 352 | 3.81M |     ASN1_OBJECT_free(o); | 
| 353 | 3.81M |     ASN1_OCTET_STRING_free(os); | 
| 354 | 3.81M |     ASN1_INTEGER_free(ai); | 
| 355 | 3.81M |     ASN1_ENUMERATED_free(ae); | 
| 356 | 3.81M |     *pp = p; | 
| 357 | 3.81M |     return ret; | 
| 358 | 1.82M | } | 
| 359 |  |  | 
| 360 |  | const char *ASN1_tag2str(int tag) | 
| 361 | 18.9M | { | 
| 362 | 18.9M |     static const char *const tag2str[] = { | 
| 363 |  |         /* 0-4 */ | 
| 364 | 18.9M |         "EOC", "BOOLEAN", "INTEGER", "BIT STRING", "OCTET STRING", | 
| 365 |  |         /* 5-9 */ | 
| 366 | 18.9M |         "NULL", "OBJECT", "OBJECT DESCRIPTOR", "EXTERNAL", "REAL", | 
| 367 |  |         /* 10-13 */ | 
| 368 | 18.9M |         "ENUMERATED", "<ASN1 11>", "UTF8STRING", "<ASN1 13>", | 
| 369 |  |         /* 15-17 */ | 
| 370 | 18.9M |         "<ASN1 14>", "<ASN1 15>", "SEQUENCE", "SET", | 
| 371 |  |         /* 18-20 */ | 
| 372 | 18.9M |         "NUMERICSTRING", "PRINTABLESTRING", "T61STRING", | 
| 373 |  |         /* 21-24 */ | 
| 374 | 18.9M |         "VIDEOTEXSTRING", "IA5STRING", "UTCTIME", "GENERALIZEDTIME", | 
| 375 |  |         /* 25-27 */ | 
| 376 | 18.9M |         "GRAPHICSTRING", "VISIBLESTRING", "GENERALSTRING", | 
| 377 |  |         /* 28-30 */ | 
| 378 | 18.9M |         "UNIVERSALSTRING", "<ASN1 29>", "BMPSTRING" | 
| 379 | 18.9M |     }; | 
| 380 |  |  | 
| 381 | 18.9M |     if ((tag == V_ASN1_NEG_INTEGER) || (tag == V_ASN1_NEG_ENUMERATED)) | 
| 382 | 2.48k |         tag &= ~0x100; | 
| 383 |  |  | 
| 384 | 18.9M |     if (tag < 0 || tag > 30) | 
| 385 | 230k |         return "(unknown)"; | 
| 386 | 18.6M |     return tag2str[tag]; | 
| 387 | 18.9M | } |