1# 2# This file is part of pyasn1 software. 3# 4# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com> 5# License: http://snmplabs.com/pyasn1/license.html 6# 7import sys 8 9from pyasn1 import error 10from pyasn1.type import tag 11from pyasn1.type import tagmap 12 13__all__ = ['NamedType', 'OptionalNamedType', 'DefaultedNamedType', 14 'NamedTypes'] 15 16try: 17 any 18 19except NameError: 20 any = lambda x: bool(filter(bool, x)) 21 22 23class NamedType(object): 24 """Create named field object for a constructed ASN.1 type. 25 26 The |NamedType| object represents a single name and ASN.1 type of a constructed ASN.1 type. 27 28 |NamedType| objects are immutable and duck-type Python :class:`tuple` objects 29 holding *name* and *asn1Object* components. 30 31 Parameters 32 ---------- 33 name: :py:class:`str` 34 Field name 35 36 asn1Object: 37 ASN.1 type object 38 """ 39 isOptional = False 40 isDefaulted = False 41 42 def __init__(self, name, asn1Object, openType=None): 43 self.__name = name 44 self.__type = asn1Object 45 self.__nameAndType = name, asn1Object 46 self.__openType = openType 47 48 def __repr__(self): 49 representation = '%s=%r' % (self.name, self.asn1Object) 50 51 if self.openType: 52 representation += ', open type %r' % self.openType 53 54 return '<%s object, type %s>' % ( 55 self.__class__.__name__, representation) 56 57 def __eq__(self, other): 58 return self.__nameAndType == other 59 60 def __ne__(self, other): 61 return self.__nameAndType != other 62 63 def __lt__(self, other): 64 return self.__nameAndType < other 65 66 def __le__(self, other): 67 return self.__nameAndType <= other 68 69 def __gt__(self, other): 70 return self.__nameAndType > other 71 72 def __ge__(self, other): 73 return self.__nameAndType >= other 74 75 def __hash__(self): 76 return hash(self.__nameAndType) 77 78 def __getitem__(self, idx): 79 return self.__nameAndType[idx] 80 81 def __iter__(self): 82 return iter(self.__nameAndType) 83 84 @property 85 def name(self): 86 return self.__name 87 88 @property 89 def asn1Object(self): 90 return self.__type 91 92 @property 93 def openType(self): 94 return self.__openType 95 96 # Backward compatibility 97 98 def getName(self): 99 return self.name 100 101 def getType(self): 102 return self.asn1Object 103 104 105class OptionalNamedType(NamedType): 106 __doc__ = NamedType.__doc__ 107 108 isOptional = True 109 110 111class DefaultedNamedType(NamedType): 112 __doc__ = NamedType.__doc__ 113 114 isDefaulted = True 115 116 117class NamedTypes(object): 118 """Create a collection of named fields for a constructed ASN.1 type. 119 120 The NamedTypes object represents a collection of named fields of a constructed ASN.1 type. 121 122 *NamedTypes* objects are immutable and duck-type Python :class:`dict` objects 123 holding *name* as keys and ASN.1 type object as values. 124 125 Parameters 126 ---------- 127 *namedTypes: :class:`~pyasn1.type.namedtype.NamedType` 128 129 Examples 130 -------- 131 132 .. code-block:: python 133 134 class Description(Sequence): 135 ''' 136 ASN.1 specification: 137 138 Description ::= SEQUENCE { 139 surname IA5String, 140 first-name IA5String OPTIONAL, 141 age INTEGER DEFAULT 40 142 } 143 ''' 144 componentType = NamedTypes( 145 NamedType('surname', IA5String()), 146 OptionalNamedType('first-name', IA5String()), 147 DefaultedNamedType('age', Integer(40)) 148 ) 149 150 descr = Description() 151 descr['surname'] = 'Smith' 152 descr['first-name'] = 'John' 153 """ 154 def __init__(self, *namedTypes, **kwargs): 155 self.__namedTypes = namedTypes 156 self.__namedTypesLen = len(self.__namedTypes) 157 self.__minTagSet = self.__computeMinTagSet() 158 self.__nameToPosMap = self.__computeNameToPosMap() 159 self.__tagToPosMap = self.__computeTagToPosMap() 160 self.__ambiguousTypes = 'terminal' not in kwargs and self.__computeAmbiguousTypes() or {} 161 self.__uniqueTagMap = self.__computeTagMaps(unique=True) 162 self.__nonUniqueTagMap = self.__computeTagMaps(unique=False) 163 self.__hasOptionalOrDefault = any([True for namedType in self.__namedTypes 164 if namedType.isDefaulted or namedType.isOptional]) 165 self.__hasOpenTypes = any([True for namedType in self.__namedTypes 166 if namedType.openType]) 167 168 self.__requiredComponents = frozenset( 169 [idx for idx, nt in enumerate(self.__namedTypes) if not nt.isOptional and not nt.isDefaulted] 170 ) 171 self.__keys = frozenset([namedType.name for namedType in self.__namedTypes]) 172 self.__values = tuple([namedType.asn1Object for namedType in self.__namedTypes]) 173 self.__items = tuple([(namedType.name, namedType.asn1Object) for namedType in self.__namedTypes]) 174 175 def __repr__(self): 176 representation = ', '.join(['%r' % x for x in self.__namedTypes]) 177 return '<%s object, types %s>' % ( 178 self.__class__.__name__, representation) 179 180 def __eq__(self, other): 181 return self.__namedTypes == other 182 183 def __ne__(self, other): 184 return self.__namedTypes != other 185 186 def __lt__(self, other): 187 return self.__namedTypes < other 188 189 def __le__(self, other): 190 return self.__namedTypes <= other 191 192 def __gt__(self, other): 193 return self.__namedTypes > other 194 195 def __ge__(self, other): 196 return self.__namedTypes >= other 197 198 def __hash__(self): 199 return hash(self.__namedTypes) 200 201 def __getitem__(self, idx): 202 try: 203 return self.__namedTypes[idx] 204 205 except TypeError: 206 return self.__namedTypes[self.__nameToPosMap[idx]] 207 208 def __contains__(self, key): 209 return key in self.__nameToPosMap 210 211 def __iter__(self): 212 return (x[0] for x in self.__namedTypes) 213 214 if sys.version_info[0] <= 2: 215 def __nonzero__(self): 216 return self.__namedTypesLen > 0 217 else: 218 def __bool__(self): 219 return self.__namedTypesLen > 0 220 221 def __len__(self): 222 return self.__namedTypesLen 223 224 # Python dict protocol 225 226 def values(self): 227 return self.__values 228 229 def keys(self): 230 return self.__keys 231 232 def items(self): 233 return self.__items 234 235 def clone(self): 236 return self.__class__(*self.__namedTypes) 237 238 class PostponedError(object): 239 def __init__(self, errorMsg): 240 self.__errorMsg = errorMsg 241 242 def __getitem__(self, item): 243 raise error.PyAsn1Error(self.__errorMsg) 244 245 def __computeTagToPosMap(self): 246 tagToPosMap = {} 247 for idx, namedType in enumerate(self.__namedTypes): 248 tagMap = namedType.asn1Object.tagMap 249 if isinstance(tagMap, NamedTypes.PostponedError): 250 return tagMap 251 if not tagMap: 252 continue 253 for _tagSet in tagMap.presentTypes: 254 if _tagSet in tagToPosMap: 255 return NamedTypes.PostponedError('Duplicate component tag %s at %s' % (_tagSet, namedType)) 256 tagToPosMap[_tagSet] = idx 257 258 return tagToPosMap 259 260 def __computeNameToPosMap(self): 261 nameToPosMap = {} 262 for idx, namedType in enumerate(self.__namedTypes): 263 if namedType.name in nameToPosMap: 264 return NamedTypes.PostponedError('Duplicate component name %s at %s' % (namedType.name, namedType)) 265 nameToPosMap[namedType.name] = idx 266 267 return nameToPosMap 268 269 def __computeAmbiguousTypes(self): 270 ambiguousTypes = {} 271 partialAmbiguousTypes = () 272 for idx, namedType in reversed(tuple(enumerate(self.__namedTypes))): 273 if namedType.isOptional or namedType.isDefaulted: 274 partialAmbiguousTypes = (namedType,) + partialAmbiguousTypes 275 else: 276 partialAmbiguousTypes = (namedType,) 277 if len(partialAmbiguousTypes) == len(self.__namedTypes): 278 ambiguousTypes[idx] = self 279 else: 280 ambiguousTypes[idx] = NamedTypes(*partialAmbiguousTypes, **dict(terminal=True)) 281 return ambiguousTypes 282 283 def getTypeByPosition(self, idx): 284 """Return ASN.1 type object by its position in fields set. 285 286 Parameters 287 ---------- 288 idx: :py:class:`int` 289 Field index 290 291 Returns 292 ------- 293 : 294 ASN.1 type 295 296 Raises 297 ------ 298 ~pyasn1.error.PyAsn1Error 299 If given position is out of fields range 300 """ 301 try: 302 return self.__namedTypes[idx].asn1Object 303 304 except IndexError: 305 raise error.PyAsn1Error('Type position out of range') 306 307 def getPositionByType(self, tagSet): 308 """Return field position by its ASN.1 type. 309 310 Parameters 311 ---------- 312 tagSet: :class:`~pysnmp.type.tag.TagSet` 313 ASN.1 tag set distinguishing one ASN.1 type from others. 314 315 Returns 316 ------- 317 : :py:class:`int` 318 ASN.1 type position in fields set 319 320 Raises 321 ------ 322 ~pyasn1.error.PyAsn1Error 323 If *tagSet* is not present or ASN.1 types are not unique within callee *NamedTypes* 324 """ 325 try: 326 return self.__tagToPosMap[tagSet] 327 328 except KeyError: 329 raise error.PyAsn1Error('Type %s not found' % (tagSet,)) 330 331 def getNameByPosition(self, idx): 332 """Return field name by its position in fields set. 333 334 Parameters 335 ---------- 336 idx: :py:class:`idx` 337 Field index 338 339 Returns 340 ------- 341 : :py:class:`str` 342 Field name 343 344 Raises 345 ------ 346 ~pyasn1.error.PyAsn1Error 347 If given field name is not present in callee *NamedTypes* 348 """ 349 try: 350 return self.__namedTypes[idx].name 351 352 except IndexError: 353 raise error.PyAsn1Error('Type position out of range') 354 355 def getPositionByName(self, name): 356 """Return field position by filed name. 357 358 Parameters 359 ---------- 360 name: :py:class:`str` 361 Field name 362 363 Returns 364 ------- 365 : :py:class:`int` 366 Field position in fields set 367 368 Raises 369 ------ 370 ~pyasn1.error.PyAsn1Error 371 If *name* is not present or not unique within callee *NamedTypes* 372 """ 373 try: 374 return self.__nameToPosMap[name] 375 376 except KeyError: 377 raise error.PyAsn1Error('Name %s not found' % (name,)) 378 379 def getTagMapNearPosition(self, idx): 380 """Return ASN.1 types that are allowed at or past given field position. 381 382 Some ASN.1 serialisation allow for skipping optional and defaulted fields. 383 Some constructed ASN.1 types allow reordering of the fields. When recovering 384 such objects it may be important to know which types can possibly be 385 present at any given position in the field sets. 386 387 Parameters 388 ---------- 389 idx: :py:class:`int` 390 Field index 391 392 Returns 393 ------- 394 : :class:`~pyasn1.type.tagmap.TagMap` 395 Map if ASN.1 types allowed at given field position 396 397 Raises 398 ------ 399 ~pyasn1.error.PyAsn1Error 400 If given position is out of fields range 401 """ 402 try: 403 return self.__ambiguousTypes[idx].tagMap 404 405 except KeyError: 406 raise error.PyAsn1Error('Type position out of range') 407 408 def getPositionNearType(self, tagSet, idx): 409 """Return the closest field position where given ASN.1 type is allowed. 410 411 Some ASN.1 serialisation allow for skipping optional and defaulted fields. 412 Some constructed ASN.1 types allow reordering of the fields. When recovering 413 such objects it may be important to know at which field position, in field set, 414 given *tagSet* is allowed at or past *idx* position. 415 416 Parameters 417 ---------- 418 tagSet: :class:`~pyasn1.type.tag.TagSet` 419 ASN.1 type which field position to look up 420 421 idx: :py:class:`int` 422 Field position at or past which to perform ASN.1 type look up 423 424 Returns 425 ------- 426 : :py:class:`int` 427 Field position in fields set 428 429 Raises 430 ------ 431 ~pyasn1.error.PyAsn1Error 432 If *tagSet* is not present or not unique within callee *NamedTypes* 433 or *idx* is out of fields range 434 """ 435 try: 436 return idx + self.__ambiguousTypes[idx].getPositionByType(tagSet) 437 438 except KeyError: 439 raise error.PyAsn1Error('Type position out of range') 440 441 def __computeMinTagSet(self): 442 minTagSet = None 443 for namedType in self.__namedTypes: 444 asn1Object = namedType.asn1Object 445 446 try: 447 tagSet = asn1Object.minTagSet 448 449 except AttributeError: 450 tagSet = asn1Object.tagSet 451 452 if minTagSet is None or tagSet < minTagSet: 453 minTagSet = tagSet 454 455 return minTagSet or tag.TagSet() 456 457 @property 458 def minTagSet(self): 459 """Return the minimal TagSet among ASN.1 type in callee *NamedTypes*. 460 461 Some ASN.1 types/serialisation protocols require ASN.1 types to be 462 arranged based on their numerical tag value. The *minTagSet* property 463 returns that. 464 465 Returns 466 ------- 467 : :class:`~pyasn1.type.tagset.TagSet` 468 Minimal TagSet among ASN.1 types in callee *NamedTypes* 469 """ 470 return self.__minTagSet 471 472 def __computeTagMaps(self, unique): 473 presentTypes = {} 474 skipTypes = {} 475 defaultType = None 476 for namedType in self.__namedTypes: 477 tagMap = namedType.asn1Object.tagMap 478 if isinstance(tagMap, NamedTypes.PostponedError): 479 return tagMap 480 for tagSet in tagMap: 481 if unique and tagSet in presentTypes: 482 return NamedTypes.PostponedError('Non-unique tagSet %s of %s at %s' % (tagSet, namedType, self)) 483 presentTypes[tagSet] = namedType.asn1Object 484 skipTypes.update(tagMap.skipTypes) 485 486 if defaultType is None: 487 defaultType = tagMap.defaultType 488 elif tagMap.defaultType is not None: 489 return NamedTypes.PostponedError('Duplicate default ASN.1 type at %s' % (self,)) 490 491 return tagmap.TagMap(presentTypes, skipTypes, defaultType) 492 493 @property 494 def tagMap(self): 495 """Return a *TagMap* object from tags and types recursively. 496 497 Return a :class:`~pyasn1.type.tagmap.TagMap` object by 498 combining tags from *TagMap* objects of children types and 499 associating them with their immediate child type. 500 501 Example 502 ------- 503 .. code-block:: python 504 505 OuterType ::= CHOICE { 506 innerType INTEGER 507 } 508 509 Calling *.tagMap* on *OuterType* will yield a map like this: 510 511 .. code-block:: python 512 513 Integer.tagSet -> Choice 514 """ 515 return self.__nonUniqueTagMap 516 517 @property 518 def tagMapUnique(self): 519 """Return a *TagMap* object from unique tags and types recursively. 520 521 Return a :class:`~pyasn1.type.tagmap.TagMap` object by 522 combining tags from *TagMap* objects of children types and 523 associating them with their immediate child type. 524 525 Example 526 ------- 527 .. code-block:: python 528 529 OuterType ::= CHOICE { 530 innerType INTEGER 531 } 532 533 Calling *.tagMapUnique* on *OuterType* will yield a map like this: 534 535 .. code-block:: python 536 537 Integer.tagSet -> Choice 538 539 Note 540 ---- 541 542 Duplicate *TagSet* objects found in the tree of children 543 types would cause error. 544 """ 545 return self.__uniqueTagMap 546 547 @property 548 def hasOptionalOrDefault(self): 549 return self.__hasOptionalOrDefault 550 551 @property 552 def hasOpenTypes(self): 553 return self.__hasOpenTypes 554 555 @property 556 def namedTypes(self): 557 return tuple(self.__namedTypes) 558 559 @property 560 def requiredComponents(self): 561 return self.__requiredComponents 562