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