1# 2# This file is part of pyasn1 software. 3# 4# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> 5# License: http://snmplabs.com/pyasn1/license.html 6# 7from pyasn1 import debug 8from pyasn1 import error 9from pyasn1.codec.ber import eoo 10from pyasn1.compat.integer import to_bytes 11from pyasn1.compat.octets import (int2oct, oct2int, ints2octs, null, 12 str2octs, isOctetsType) 13from pyasn1.type import char 14from pyasn1.type import tag 15from pyasn1.type import univ 16from pyasn1.type import useful 17 18__all__ = ['encode'] 19 20 21class AbstractItemEncoder(object): 22 supportIndefLenMode = True 23 24 # An outcome of otherwise legit call `encodeFun(eoo.endOfOctets)` 25 eooIntegerSubstrate = (0, 0) 26 eooOctetsSubstrate = ints2octs(eooIntegerSubstrate) 27 28 # noinspection PyMethodMayBeStatic 29 def encodeTag(self, singleTag, isConstructed): 30 tagClass, tagFormat, tagId = singleTag 31 encodedTag = tagClass | tagFormat 32 if isConstructed: 33 encodedTag |= tag.tagFormatConstructed 34 if tagId < 31: 35 return encodedTag | tagId, 36 else: 37 substrate = tagId & 0x7f, 38 tagId >>= 7 39 while tagId: 40 substrate = (0x80 | (tagId & 0x7f),) + substrate 41 tagId >>= 7 42 return (encodedTag | 0x1F,) + substrate 43 44 def encodeLength(self, length, defMode): 45 if not defMode and self.supportIndefLenMode: 46 return (0x80,) 47 if length < 0x80: 48 return length, 49 else: 50 substrate = () 51 while length: 52 substrate = (length & 0xff,) + substrate 53 length >>= 8 54 substrateLen = len(substrate) 55 if substrateLen > 126: 56 raise error.PyAsn1Error('Length octets overflow (%d)' % substrateLen) 57 return (0x80 | substrateLen,) + substrate 58 59 def encodeValue(self, value, asn1Spec, encodeFun, **options): 60 raise error.PyAsn1Error('Not implemented') 61 62 def encode(self, value, asn1Spec=None, encodeFun=None, **options): 63 64 if asn1Spec is None: 65 tagSet = value.tagSet 66 else: 67 tagSet = asn1Spec.tagSet 68 69 # untagged item? 70 if not tagSet: 71 substrate, isConstructed, isOctets = self.encodeValue( 72 value, asn1Spec, encodeFun, **options 73 ) 74 return substrate 75 76 defMode = options.get('defMode', True) 77 78 for idx, singleTag in enumerate(tagSet.superTags): 79 80 defModeOverride = defMode 81 82 # base tag? 83 if not idx: 84 substrate, isConstructed, isOctets = self.encodeValue( 85 value, asn1Spec, encodeFun, **options 86 ) 87 88 if not substrate and isConstructed and options.get('ifNotEmpty', False): 89 return substrate 90 91 # primitive form implies definite mode 92 if not isConstructed: 93 defModeOverride = True 94 95 header = self.encodeTag(singleTag, isConstructed) 96 header += self.encodeLength(len(substrate), defModeOverride) 97 98 if isOctets: 99 substrate = ints2octs(header) + substrate 100 101 if not defModeOverride: 102 substrate += self.eooOctetsSubstrate 103 104 else: 105 substrate = header + substrate 106 107 if not defModeOverride: 108 substrate += self.eooIntegerSubstrate 109 110 if not isOctets: 111 substrate = ints2octs(substrate) 112 113 return substrate 114 115 116class EndOfOctetsEncoder(AbstractItemEncoder): 117 def encodeValue(self, value, asn1Spec, encodeFun, **options): 118 return null, False, True 119 120 121class BooleanEncoder(AbstractItemEncoder): 122 supportIndefLenMode = False 123 124 def encodeValue(self, value, asn1Spec, encodeFun, **options): 125 return value and (1,) or (0,), False, False 126 127 128class IntegerEncoder(AbstractItemEncoder): 129 supportIndefLenMode = False 130 supportCompactZero = False 131 132 def encodeValue(self, value, asn1Spec, encodeFun, **options): 133 if value == 0: 134 # de-facto way to encode zero 135 if self.supportCompactZero: 136 return (), False, False 137 else: 138 return (0,), False, False 139 140 return to_bytes(int(value), signed=True), False, True 141 142 143class BitStringEncoder(AbstractItemEncoder): 144 def encodeValue(self, value, asn1Spec, encodeFun, **options): 145 if asn1Spec is not None: 146 # TODO: try to avoid ASN.1 schema instantiation 147 value = asn1Spec.clone(value) 148 149 valueLength = len(value) 150 if valueLength % 8: 151 alignedValue = value << (8 - valueLength % 8) 152 else: 153 alignedValue = value 154 155 maxChunkSize = options.get('maxChunkSize', 0) 156 if not maxChunkSize or len(alignedValue) <= maxChunkSize * 8: 157 substrate = alignedValue.asOctets() 158 return int2oct(len(substrate) * 8 - valueLength) + substrate, False, True 159 160 baseTag = value.tagSet.baseTag 161 162 # strip off explicit tags 163 if baseTag: 164 tagSet = tag.TagSet(baseTag, baseTag) 165 else: 166 tagSet = tag.TagSet() 167 168 alignedValue = alignedValue.clone(tagSet=tagSet) 169 170 stop = 0 171 substrate = null 172 while stop < valueLength: 173 start = stop 174 stop = min(start + maxChunkSize * 8, valueLength) 175 substrate += encodeFun(alignedValue[start:stop], asn1Spec, **options) 176 177 return substrate, True, True 178 179 180class OctetStringEncoder(AbstractItemEncoder): 181 182 def encodeValue(self, value, asn1Spec, encodeFun, **options): 183 184 if asn1Spec is None: 185 substrate = value.asOctets() 186 187 elif not isOctetsType(value): 188 substrate = asn1Spec.clone(value).asOctets() 189 190 else: 191 substrate = value 192 193 maxChunkSize = options.get('maxChunkSize', 0) 194 195 if not maxChunkSize or len(substrate) <= maxChunkSize: 196 return substrate, False, True 197 198 else: 199 200 # strip off explicit tags for inner chunks 201 202 if asn1Spec is None: 203 baseTag = value.tagSet.baseTag 204 205 # strip off explicit tags 206 if baseTag: 207 tagSet = tag.TagSet(baseTag, baseTag) 208 else: 209 tagSet = tag.TagSet() 210 211 asn1Spec = value.clone(tagSet=tagSet) 212 213 elif not isOctetsType(value): 214 baseTag = asn1Spec.tagSet.baseTag 215 216 # strip off explicit tags 217 if baseTag: 218 tagSet = tag.TagSet(baseTag, baseTag) 219 else: 220 tagSet = tag.TagSet() 221 222 asn1Spec = asn1Spec.clone(tagSet=tagSet) 223 224 pos = 0 225 substrate = null 226 227 while True: 228 chunk = value[pos:pos + maxChunkSize] 229 if not chunk: 230 break 231 232 substrate += encodeFun(chunk, asn1Spec, **options) 233 pos += maxChunkSize 234 235 return substrate, True, True 236 237 238class NullEncoder(AbstractItemEncoder): 239 supportIndefLenMode = False 240 241 def encodeValue(self, value, asn1Spec, encodeFun, **options): 242 return null, False, True 243 244 245class ObjectIdentifierEncoder(AbstractItemEncoder): 246 supportIndefLenMode = False 247 248 def encodeValue(self, value, asn1Spec, encodeFun, **options): 249 if asn1Spec is not None: 250 value = asn1Spec.clone(value) 251 252 oid = value.asTuple() 253 254 # Build the first pair 255 try: 256 first = oid[0] 257 second = oid[1] 258 259 except IndexError: 260 raise error.PyAsn1Error('Short OID %s' % (value,)) 261 262 if 0 <= second <= 39: 263 if first == 1: 264 oid = (second + 40,) + oid[2:] 265 elif first == 0: 266 oid = (second,) + oid[2:] 267 elif first == 2: 268 oid = (second + 80,) + oid[2:] 269 else: 270 raise error.PyAsn1Error('Impossible first/second arcs at %s' % (value,)) 271 elif first == 2: 272 oid = (second + 80,) + oid[2:] 273 else: 274 raise error.PyAsn1Error('Impossible first/second arcs at %s' % (value,)) 275 276 octets = () 277 278 # Cycle through subIds 279 for subOid in oid: 280 if 0 <= subOid <= 127: 281 # Optimize for the common case 282 octets += (subOid,) 283 elif subOid > 127: 284 # Pack large Sub-Object IDs 285 res = (subOid & 0x7f,) 286 subOid >>= 7 287 while subOid: 288 res = (0x80 | (subOid & 0x7f),) + res 289 subOid >>= 7 290 # Add packed Sub-Object ID to resulted Object ID 291 octets += res 292 else: 293 raise error.PyAsn1Error('Negative OID arc %s at %s' % (subOid, value)) 294 295 return octets, False, False 296 297 298class RealEncoder(AbstractItemEncoder): 299 supportIndefLenMode = 0 300 binEncBase = 2 # set to None to choose encoding base automatically 301 302 @staticmethod 303 def _dropFloatingPoint(m, encbase, e): 304 ms, es = 1, 1 305 if m < 0: 306 ms = -1 # mantissa sign 307 if e < 0: 308 es = -1 # exponenta sign 309 m *= ms 310 if encbase == 8: 311 m *= 2 ** (abs(e) % 3 * es) 312 e = abs(e) // 3 * es 313 elif encbase == 16: 314 m *= 2 ** (abs(e) % 4 * es) 315 e = abs(e) // 4 * es 316 317 while True: 318 if int(m) != m: 319 m *= encbase 320 e -= 1 321 continue 322 break 323 return ms, int(m), encbase, e 324 325 def _chooseEncBase(self, value): 326 m, b, e = value 327 encBase = [2, 8, 16] 328 if value.binEncBase in encBase: 329 return self._dropFloatingPoint(m, value.binEncBase, e) 330 elif self.binEncBase in encBase: 331 return self._dropFloatingPoint(m, self.binEncBase, e) 332 # auto choosing base 2/8/16 333 mantissa = [m, m, m] 334 exponenta = [e, e, e] 335 sign = 1 336 encbase = 2 337 e = float('inf') 338 for i in range(3): 339 (sign, 340 mantissa[i], 341 encBase[i], 342 exponenta[i]) = self._dropFloatingPoint(mantissa[i], encBase[i], exponenta[i]) 343 if abs(exponenta[i]) < abs(e) or (abs(exponenta[i]) == abs(e) and mantissa[i] < m): 344 e = exponenta[i] 345 m = int(mantissa[i]) 346 encbase = encBase[i] 347 return sign, m, encbase, e 348 349 def encodeValue(self, value, asn1Spec, encodeFun, **options): 350 if asn1Spec is not None: 351 value = asn1Spec.clone(value) 352 353 if value.isPlusInf: 354 return (0x40,), False, False 355 if value.isMinusInf: 356 return (0x41,), False, False 357 m, b, e = value 358 if not m: 359 return null, False, True 360 if b == 10: 361 return str2octs('\x03%dE%s%d' % (m, e == 0 and '+' or '', e)), False, True 362 elif b == 2: 363 fo = 0x80 # binary encoding 364 ms, m, encbase, e = self._chooseEncBase(value) 365 if ms < 0: # mantissa sign 366 fo |= 0x40 # sign bit 367 # exponenta & mantissa normalization 368 if encbase == 2: 369 while m & 0x1 == 0: 370 m >>= 1 371 e += 1 372 elif encbase == 8: 373 while m & 0x7 == 0: 374 m >>= 3 375 e += 1 376 fo |= 0x10 377 else: # encbase = 16 378 while m & 0xf == 0: 379 m >>= 4 380 e += 1 381 fo |= 0x20 382 sf = 0 # scale factor 383 while m & 0x1 == 0: 384 m >>= 1 385 sf += 1 386 if sf > 3: 387 raise error.PyAsn1Error('Scale factor overflow') # bug if raised 388 fo |= sf << 2 389 eo = null 390 if e == 0 or e == -1: 391 eo = int2oct(e & 0xff) 392 else: 393 while e not in (0, -1): 394 eo = int2oct(e & 0xff) + eo 395 e >>= 8 396 if e == 0 and eo and oct2int(eo[0]) & 0x80: 397 eo = int2oct(0) + eo 398 if e == -1 and eo and not (oct2int(eo[0]) & 0x80): 399 eo = int2oct(0xff) + eo 400 n = len(eo) 401 if n > 0xff: 402 raise error.PyAsn1Error('Real exponent overflow') 403 if n == 1: 404 pass 405 elif n == 2: 406 fo |= 1 407 elif n == 3: 408 fo |= 2 409 else: 410 fo |= 3 411 eo = int2oct(n & 0xff) + eo 412 po = null 413 while m: 414 po = int2oct(m & 0xff) + po 415 m >>= 8 416 substrate = int2oct(fo) + eo + po 417 return substrate, False, True 418 else: 419 raise error.PyAsn1Error('Prohibited Real base %s' % b) 420 421 422class SequenceEncoder(AbstractItemEncoder): 423 omitEmptyOptionals = False 424 425 # TODO: handling three flavors of input is too much -- split over codecs 426 427 def encodeValue(self, value, asn1Spec, encodeFun, **options): 428 429 substrate = null 430 431 if asn1Spec is None: 432 # instance of ASN.1 schema 433 value.verifySizeSpec() 434 435 namedTypes = value.componentType 436 437 for idx, component in enumerate(value.values()): 438 if namedTypes: 439 namedType = namedTypes[idx] 440 441 if namedType.isOptional and not component.isValue: 442 continue 443 444 if namedType.isDefaulted and component == namedType.asn1Object: 445 continue 446 447 if self.omitEmptyOptionals: 448 options.update(ifNotEmpty=namedType.isOptional) 449 450 chunk = encodeFun(component, asn1Spec, **options) 451 452 # wrap open type blob if needed 453 if namedTypes and namedType.openType: 454 wrapType = namedType.asn1Object 455 if wrapType.tagSet and not wrapType.isSameTypeWith(component): 456 chunk = encodeFun(chunk, wrapType, **options) 457 458 substrate += chunk 459 460 else: 461 # bare Python value + ASN.1 schema 462 for idx, namedType in enumerate(asn1Spec.componentType.namedTypes): 463 464 try: 465 component = value[namedType.name] 466 467 except KeyError: 468 raise error.PyAsn1Error('Component name "%s" not found in %r' % (namedType.name, value)) 469 470 if namedType.isOptional and namedType.name not in value: 471 continue 472 473 if namedType.isDefaulted and component == namedType.asn1Object: 474 continue 475 476 if self.omitEmptyOptionals: 477 options.update(ifNotEmpty=namedType.isOptional) 478 479 chunk = encodeFun(component, asn1Spec[idx], **options) 480 481 # wrap open type blob if needed 482 if namedType.openType: 483 wrapType = namedType.asn1Object 484 if wrapType.tagSet and not wrapType.isSameTypeWith(component): 485 chunk = encodeFun(chunk, wrapType, **options) 486 487 substrate += chunk 488 489 return substrate, True, True 490 491 492class SequenceOfEncoder(AbstractItemEncoder): 493 def encodeValue(self, value, asn1Spec, encodeFun, **options): 494 if asn1Spec is None: 495 value.verifySizeSpec() 496 else: 497 asn1Spec = asn1Spec.componentType 498 499 substrate = null 500 501 for idx, component in enumerate(value): 502 substrate += encodeFun(value[idx], asn1Spec, **options) 503 504 return substrate, True, True 505 506 507class ChoiceEncoder(AbstractItemEncoder): 508 def encodeValue(self, value, asn1Spec, encodeFun, **options): 509 if asn1Spec is None: 510 component = value.getComponent() 511 else: 512 names = [namedType.name for namedType in asn1Spec.componentType.namedTypes 513 if namedType.name in value] 514 if len(names) != 1: 515 raise error.PyAsn1Error('%s components for Choice at %r' % (len(names) and 'Multiple ' or 'None ', value)) 516 517 name = names[0] 518 519 component = value[name] 520 asn1Spec = asn1Spec[name] 521 522 return encodeFun(component, asn1Spec, **options), True, True 523 524 525class AnyEncoder(OctetStringEncoder): 526 def encodeValue(self, value, asn1Spec, encodeFun, **options): 527 if asn1Spec is None: 528 value = value.asOctets() 529 elif not isOctetsType(value): 530 value = asn1Spec.clone(value).asOctets() 531 532 return value, not options.get('defMode', True), True 533 534 535tagMap = { 536 eoo.endOfOctets.tagSet: EndOfOctetsEncoder(), 537 univ.Boolean.tagSet: BooleanEncoder(), 538 univ.Integer.tagSet: IntegerEncoder(), 539 univ.BitString.tagSet: BitStringEncoder(), 540 univ.OctetString.tagSet: OctetStringEncoder(), 541 univ.Null.tagSet: NullEncoder(), 542 univ.ObjectIdentifier.tagSet: ObjectIdentifierEncoder(), 543 univ.Enumerated.tagSet: IntegerEncoder(), 544 univ.Real.tagSet: RealEncoder(), 545 # Sequence & Set have same tags as SequenceOf & SetOf 546 univ.SequenceOf.tagSet: SequenceOfEncoder(), 547 univ.SetOf.tagSet: SequenceOfEncoder(), 548 univ.Choice.tagSet: ChoiceEncoder(), 549 # character string types 550 char.UTF8String.tagSet: OctetStringEncoder(), 551 char.NumericString.tagSet: OctetStringEncoder(), 552 char.PrintableString.tagSet: OctetStringEncoder(), 553 char.TeletexString.tagSet: OctetStringEncoder(), 554 char.VideotexString.tagSet: OctetStringEncoder(), 555 char.IA5String.tagSet: OctetStringEncoder(), 556 char.GraphicString.tagSet: OctetStringEncoder(), 557 char.VisibleString.tagSet: OctetStringEncoder(), 558 char.GeneralString.tagSet: OctetStringEncoder(), 559 char.UniversalString.tagSet: OctetStringEncoder(), 560 char.BMPString.tagSet: OctetStringEncoder(), 561 # useful types 562 useful.ObjectDescriptor.tagSet: OctetStringEncoder(), 563 useful.GeneralizedTime.tagSet: OctetStringEncoder(), 564 useful.UTCTime.tagSet: OctetStringEncoder() 565} 566 567# Put in ambiguous & non-ambiguous types for faster codec lookup 568typeMap = { 569 univ.Boolean.typeId: BooleanEncoder(), 570 univ.Integer.typeId: IntegerEncoder(), 571 univ.BitString.typeId: BitStringEncoder(), 572 univ.OctetString.typeId: OctetStringEncoder(), 573 univ.Null.typeId: NullEncoder(), 574 univ.ObjectIdentifier.typeId: ObjectIdentifierEncoder(), 575 univ.Enumerated.typeId: IntegerEncoder(), 576 univ.Real.typeId: RealEncoder(), 577 # Sequence & Set have same tags as SequenceOf & SetOf 578 univ.Set.typeId: SequenceEncoder(), 579 univ.SetOf.typeId: SequenceOfEncoder(), 580 univ.Sequence.typeId: SequenceEncoder(), 581 univ.SequenceOf.typeId: SequenceOfEncoder(), 582 univ.Choice.typeId: ChoiceEncoder(), 583 univ.Any.typeId: AnyEncoder(), 584 # character string types 585 char.UTF8String.typeId: OctetStringEncoder(), 586 char.NumericString.typeId: OctetStringEncoder(), 587 char.PrintableString.typeId: OctetStringEncoder(), 588 char.TeletexString.typeId: OctetStringEncoder(), 589 char.VideotexString.typeId: OctetStringEncoder(), 590 char.IA5String.typeId: OctetStringEncoder(), 591 char.GraphicString.typeId: OctetStringEncoder(), 592 char.VisibleString.typeId: OctetStringEncoder(), 593 char.GeneralString.typeId: OctetStringEncoder(), 594 char.UniversalString.typeId: OctetStringEncoder(), 595 char.BMPString.typeId: OctetStringEncoder(), 596 # useful types 597 useful.ObjectDescriptor.typeId: OctetStringEncoder(), 598 useful.GeneralizedTime.typeId: OctetStringEncoder(), 599 useful.UTCTime.typeId: OctetStringEncoder() 600} 601 602 603class Encoder(object): 604 fixedDefLengthMode = None 605 fixedChunkSize = None 606 607 # noinspection PyDefaultArgument 608 def __init__(self, tagMap, typeMap={}): 609 self.__tagMap = tagMap 610 self.__typeMap = typeMap 611 612 def __call__(self, value, asn1Spec=None, **options): 613 try: 614 if asn1Spec is None: 615 typeId = value.typeId 616 else: 617 typeId = asn1Spec.typeId 618 619 except AttributeError: 620 raise error.PyAsn1Error('Value %r is not ASN.1 type instance ' 621 'and "asn1Spec" not given' % (value,)) 622 623 if debug.logger & debug.flagEncoder: 624 logger = debug.logger 625 else: 626 logger = None 627 628 if logger: 629 logger('encoder called in %sdef mode, chunk size %s for ' 630 'type %s, value:\n%s' % (not options.get('defMode', True) and 'in' or '', options.get('maxChunkSize', 0), asn1Spec is None and value.prettyPrintType() or asn1Spec.prettyPrintType(), value)) 631 632 if self.fixedDefLengthMode is not None: 633 options.update(defMode=self.fixedDefLengthMode) 634 635 if self.fixedChunkSize is not None: 636 options.update(maxChunkSize=self.fixedChunkSize) 637 638 639 try: 640 concreteEncoder = self.__typeMap[typeId] 641 642 if logger: 643 logger('using value codec %s chosen by type ID %s' % (concreteEncoder.__class__.__name__, typeId)) 644 645 except KeyError: 646 if asn1Spec is None: 647 tagSet = value.tagSet 648 else: 649 tagSet = asn1Spec.tagSet 650 651 # use base type for codec lookup to recover untagged types 652 baseTagSet = tag.TagSet(tagSet.baseTag, tagSet.baseTag) 653 654 try: 655 concreteEncoder = self.__tagMap[baseTagSet] 656 657 except KeyError: 658 raise error.PyAsn1Error('No encoder for %r (%s)' % (value, tagSet)) 659 660 if logger: 661 logger('using value codec %s chosen by tagSet %s' % (concreteEncoder.__class__.__name__, tagSet)) 662 663 substrate = concreteEncoder.encode(value, asn1Spec, self, **options) 664 665 if logger: 666 logger('codec %s built %s octets of substrate: %s\nencoder completed' % (concreteEncoder, len(substrate), debug.hexdump(substrate))) 667 668 return substrate 669 670#: Turns ASN.1 object into BER octet stream. 671#: 672#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) 673#: walks all its components recursively and produces a BER octet stream. 674#: 675#: Parameters 676#: ---------- 677#: value: either a Python or pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) 678#: A Python or pyasn1 object to encode. If Python object is given, `asnSpec` 679#: parameter is required to guide the encoding process. 680#: 681#: Keyword Args 682#: ------------ 683#: asn1Spec: 684#: Optional ASN.1 schema or value object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative 685#: 686#: defMode: :py:class:`bool` 687#: If `False`, produces indefinite length encoding 688#: 689#: maxChunkSize: :py:class:`int` 690#: Maximum chunk size in chunked encoding mode (0 denotes unlimited chunk size) 691#: 692#: Returns 693#: ------- 694#: : :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) 695#: Given ASN.1 object encoded into BER octetstream 696#: 697#: Raises 698#: ------ 699#: :py:class:`~pyasn1.error.PyAsn1Error` 700#: On encoding errors 701#: 702#: Examples 703#: -------- 704#: Encode Python value into BER with ASN.1 schema 705#: 706#: .. code-block:: pycon 707#: 708#: >>> seq = SequenceOf(componentType=Integer()) 709#: >>> encode([1, 2, 3], asn1Spec=seq) 710#: b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03' 711#: 712#: Encode ASN.1 value object into BER 713#: 714#: .. code-block:: pycon 715#: 716#: >>> seq = SequenceOf(componentType=Integer()) 717#: >>> seq.extend([1, 2, 3]) 718#: >>> encode(seq) 719#: b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03' 720#: 721encode = Encoder(tagMap, typeMap) 722