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# 7# Original concept and code by Mike C. Fletcher. 8# 9import sys 10 11from pyasn1.type import error 12 13__all__ = ['SingleValueConstraint', 'ContainedSubtypeConstraint', 14 'ValueRangeConstraint', 'ValueSizeConstraint', 15 'PermittedAlphabetConstraint', 'InnerTypeConstraint', 16 'ConstraintsExclusion', 'ConstraintsIntersection', 17 'ConstraintsUnion'] 18 19 20class AbstractConstraint(object): 21 22 def __init__(self, *values): 23 self._valueMap = set() 24 self._setValues(values) 25 self.__hash = hash((self.__class__.__name__, self._values)) 26 27 def __call__(self, value, idx=None): 28 if not self._values: 29 return 30 31 try: 32 self._testValue(value, idx) 33 34 except error.ValueConstraintError: 35 raise error.ValueConstraintError( 36 '%s failed at: %r' % (self, sys.exc_info()[1]) 37 ) 38 39 def __repr__(self): 40 representation = '%s object at 0x%x' % (self.__class__.__name__, id(self)) 41 42 if self._values: 43 representation += ' consts %s' % ', '.join([repr(x) for x in self._values]) 44 45 return '<%s>' % representation 46 47 def __eq__(self, other): 48 return self is other and True or self._values == other 49 50 def __ne__(self, other): 51 return self._values != other 52 53 def __lt__(self, other): 54 return self._values < other 55 56 def __le__(self, other): 57 return self._values <= other 58 59 def __gt__(self, other): 60 return self._values > other 61 62 def __ge__(self, other): 63 return self._values >= other 64 65 if sys.version_info[0] <= 2: 66 def __nonzero__(self): 67 return self._values and True or False 68 else: 69 def __bool__(self): 70 return self._values and True or False 71 72 def __hash__(self): 73 return self.__hash 74 75 def _setValues(self, values): 76 self._values = values 77 78 def _testValue(self, value, idx): 79 raise error.ValueConstraintError(value) 80 81 # Constraints derivation logic 82 def getValueMap(self): 83 return self._valueMap 84 85 def isSuperTypeOf(self, otherConstraint): 86 # TODO: fix possible comparison of set vs scalars here 87 return (otherConstraint is self or 88 not self._values or 89 otherConstraint == self or 90 self in otherConstraint.getValueMap()) 91 92 def isSubTypeOf(self, otherConstraint): 93 return (otherConstraint is self or 94 not self or 95 otherConstraint == self or 96 otherConstraint in self._valueMap) 97 98 99class SingleValueConstraint(AbstractConstraint): 100 """Create a SingleValueConstraint object. 101 102 The SingleValueConstraint satisfies any value that 103 is present in the set of permitted values. 104 105 The SingleValueConstraint object can be applied to 106 any ASN.1 type. 107 108 Parameters 109 ---------- 110 \*values: :class:`int` 111 Full set of values permitted by this constraint object. 112 113 Examples 114 -------- 115 .. code-block:: python 116 117 class DivisorOfSix(Integer): 118 ''' 119 ASN.1 specification: 120 121 Divisor-Of-6 ::= INTEGER (1 | 2 | 3 | 6) 122 ''' 123 subtypeSpec = SingleValueConstraint(1, 2, 3, 6) 124 125 # this will succeed 126 divisor_of_six = DivisorOfSix(1) 127 128 # this will raise ValueConstraintError 129 divisor_of_six = DivisorOfSix(7) 130 """ 131 def _setValues(self, values): 132 self._values = values 133 self._set = set(values) 134 135 def _testValue(self, value, idx): 136 if value not in self._set: 137 raise error.ValueConstraintError(value) 138 139 140class ContainedSubtypeConstraint(AbstractConstraint): 141 """Create a ContainedSubtypeConstraint object. 142 143 The ContainedSubtypeConstraint satisfies any value that 144 is present in the set of permitted values and also 145 satisfies included constraints. 146 147 The ContainedSubtypeConstraint object can be applied to 148 any ASN.1 type. 149 150 Parameters 151 ---------- 152 \*values: 153 Full set of values and constraint objects permitted 154 by this constraint object. 155 156 Examples 157 -------- 158 .. code-block:: python 159 160 class DivisorOfEighteen(Integer): 161 ''' 162 ASN.1 specification: 163 164 Divisors-of-18 ::= INTEGER (INCLUDES Divisors-of-6 | 9 | 18) 165 ''' 166 subtypeSpec = ContainedSubtypeConstraint( 167 SingleValueConstraint(1, 2, 3, 6), 9, 18 168 ) 169 170 # this will succeed 171 divisor_of_eighteen = DivisorOfEighteen(9) 172 173 # this will raise ValueConstraintError 174 divisor_of_eighteen = DivisorOfEighteen(10) 175 """ 176 def _testValue(self, value, idx): 177 for constraint in self._values: 178 if isinstance(constraint, AbstractConstraint): 179 constraint(value, idx) 180 elif value not in self._set: 181 raise error.ValueConstraintError(value) 182 183 184class ValueRangeConstraint(AbstractConstraint): 185 """Create a ValueRangeConstraint object. 186 187 The ValueRangeConstraint satisfies any value that 188 falls in the range of permitted values. 189 190 The ValueRangeConstraint object can only be applied 191 to :class:`~pyasn1.type.univ.Integer` and 192 :class:`~pyasn1.type.univ.Real` types. 193 194 Parameters 195 ---------- 196 start: :class:`int` 197 Minimum permitted value in the range (inclusive) 198 199 end: :class:`int` 200 Maximum permitted value in the range (inclusive) 201 202 Examples 203 -------- 204 .. code-block:: python 205 206 class TeenAgeYears(Integer): 207 ''' 208 ASN.1 specification: 209 210 TeenAgeYears ::= INTEGER (13 .. 19) 211 ''' 212 subtypeSpec = ValueRangeConstraint(13, 19) 213 214 # this will succeed 215 teen_year = TeenAgeYears(18) 216 217 # this will raise ValueConstraintError 218 teen_year = TeenAgeYears(20) 219 """ 220 def _testValue(self, value, idx): 221 if value < self.start or value > self.stop: 222 raise error.ValueConstraintError(value) 223 224 def _setValues(self, values): 225 if len(values) != 2: 226 raise error.PyAsn1Error( 227 '%s: bad constraint values' % (self.__class__.__name__,) 228 ) 229 self.start, self.stop = values 230 if self.start > self.stop: 231 raise error.PyAsn1Error( 232 '%s: screwed constraint values (start > stop): %s > %s' % ( 233 self.__class__.__name__, 234 self.start, self.stop 235 ) 236 ) 237 AbstractConstraint._setValues(self, values) 238 239 240class ValueSizeConstraint(ValueRangeConstraint): 241 """Create a ValueSizeConstraint object. 242 243 The ValueSizeConstraint satisfies any value for 244 as long as its size falls within the range of 245 permitted sizes. 246 247 The ValueSizeConstraint object can be applied 248 to :class:`~pyasn1.type.univ.BitString`, 249 :class:`~pyasn1.type.univ.OctetString` (including 250 all :ref:`character ASN.1 types <type.char>`), 251 :class:`~pyasn1.type.univ.SequenceOf` 252 and :class:`~pyasn1.type.univ.SetOf` types. 253 254 Parameters 255 ---------- 256 minimum: :class:`int` 257 Minimum permitted size of the value (inclusive) 258 259 maximum: :class:`int` 260 Maximum permitted size of the value (inclusive) 261 262 Examples 263 -------- 264 .. code-block:: python 265 266 class BaseballTeamRoster(SetOf): 267 ''' 268 ASN.1 specification: 269 270 BaseballTeamRoster ::= SET SIZE (1..25) OF PlayerNames 271 ''' 272 componentType = PlayerNames() 273 subtypeSpec = ValueSizeConstraint(1, 25) 274 275 # this will succeed 276 team = BaseballTeamRoster() 277 team.extend(['Jan', 'Matej']) 278 encode(team) 279 280 # this will raise ValueConstraintError 281 team = BaseballTeamRoster() 282 team.extend(['Jan'] * 26) 283 encode(team) 284 285 Note 286 ---- 287 Whenever ValueSizeConstraint is applied to mutable types 288 (e.g. :class:`~pyasn1.type.univ.SequenceOf`, 289 :class:`~pyasn1.type.univ.SetOf`), constraint 290 validation only happens at the serialisation phase rather 291 than schema instantiation phase (as it is with immutable 292 types). 293 """ 294 def _testValue(self, value, idx): 295 valueSize = len(value) 296 if valueSize < self.start or valueSize > self.stop: 297 raise error.ValueConstraintError(value) 298 299 300class PermittedAlphabetConstraint(SingleValueConstraint): 301 """Create a PermittedAlphabetConstraint object. 302 303 The PermittedAlphabetConstraint satisfies any character 304 string for as long as all its characters are present in 305 the set of permitted characters. 306 307 The PermittedAlphabetConstraint object can only be applied 308 to the :ref:`character ASN.1 types <type.char>` such as 309 :class:`~pyasn1.type.char.IA5String`. 310 311 Parameters 312 ---------- 313 \*alphabet: :class:`str` 314 Full set of characters permitted by this constraint object. 315 316 Examples 317 -------- 318 .. code-block:: python 319 320 class BooleanValue(IA5String): 321 ''' 322 ASN.1 specification: 323 324 BooleanValue ::= IA5String (FROM ('T' | 'F')) 325 ''' 326 subtypeSpec = PermittedAlphabetConstraint('T', 'F') 327 328 # this will succeed 329 truth = BooleanValue('T') 330 truth = BooleanValue('TF') 331 332 # this will raise ValueConstraintError 333 garbage = BooleanValue('TAF') 334 """ 335 def _setValues(self, values): 336 self._values = values 337 self._set = set(values) 338 339 def _testValue(self, value, idx): 340 if not self._set.issuperset(value): 341 raise error.ValueConstraintError(value) 342 343 344# This is a bit kludgy, meaning two op modes within a single constraint 345class InnerTypeConstraint(AbstractConstraint): 346 """Value must satisfy the type and presence constraints""" 347 348 def _testValue(self, value, idx): 349 if self.__singleTypeConstraint: 350 self.__singleTypeConstraint(value) 351 elif self.__multipleTypeConstraint: 352 if idx not in self.__multipleTypeConstraint: 353 raise error.ValueConstraintError(value) 354 constraint, status = self.__multipleTypeConstraint[idx] 355 if status == 'ABSENT': # XXX presense is not checked! 356 raise error.ValueConstraintError(value) 357 constraint(value) 358 359 def _setValues(self, values): 360 self.__multipleTypeConstraint = {} 361 self.__singleTypeConstraint = None 362 for v in values: 363 if isinstance(v, tuple): 364 self.__multipleTypeConstraint[v[0]] = v[1], v[2] 365 else: 366 self.__singleTypeConstraint = v 367 AbstractConstraint._setValues(self, values) 368 369 370# Logic operations on constraints 371 372class ConstraintsExclusion(AbstractConstraint): 373 """Create a ConstraintsExclusion logic operator object. 374 375 The ConstraintsExclusion logic operator succeeds when the 376 value does *not* satisfy the operand constraint. 377 378 The ConstraintsExclusion object can be applied to 379 any constraint and logic operator object. 380 381 Parameters 382 ---------- 383 constraint: 384 Constraint or logic operator object. 385 386 Examples 387 -------- 388 .. code-block:: python 389 390 class Lipogramme(IA5STRING): 391 ''' 392 ASN.1 specification: 393 394 Lipogramme ::= 395 IA5String (FROM (ALL EXCEPT ("e"|"E"))) 396 ''' 397 subtypeSpec = ConstraintsExclusion( 398 PermittedAlphabetConstraint('e', 'E') 399 ) 400 401 # this will succeed 402 lipogramme = Lipogramme('A work of fiction?') 403 404 # this will raise ValueConstraintError 405 lipogramme = Lipogramme('Eel') 406 407 Warning 408 ------- 409 The above example involving PermittedAlphabetConstraint might 410 not work due to the way how PermittedAlphabetConstraint works. 411 The other constraints might work with ConstraintsExclusion 412 though. 413 """ 414 def _testValue(self, value, idx): 415 try: 416 self._values[0](value, idx) 417 except error.ValueConstraintError: 418 return 419 else: 420 raise error.ValueConstraintError(value) 421 422 def _setValues(self, values): 423 if len(values) != 1: 424 raise error.PyAsn1Error('Single constraint expected') 425 426 AbstractConstraint._setValues(self, values) 427 428 429class AbstractConstraintSet(AbstractConstraint): 430 431 def __getitem__(self, idx): 432 return self._values[idx] 433 434 def __iter__(self): 435 return iter(self._values) 436 437 def __add__(self, value): 438 return self.__class__(*(self._values + (value,))) 439 440 def __radd__(self, value): 441 return self.__class__(*((value,) + self._values)) 442 443 def __len__(self): 444 return len(self._values) 445 446 # Constraints inclusion in sets 447 448 def _setValues(self, values): 449 self._values = values 450 for constraint in values: 451 if constraint: 452 self._valueMap.add(constraint) 453 self._valueMap.update(constraint.getValueMap()) 454 455 456class ConstraintsIntersection(AbstractConstraintSet): 457 """Create a ConstraintsIntersection logic operator object. 458 459 The ConstraintsIntersection logic operator only succeeds 460 if *all* its operands succeed. 461 462 The ConstraintsIntersection object can be applied to 463 any constraint and logic operator objects. 464 465 The ConstraintsIntersection object duck-types the immutable 466 container object like Python :py:class:`tuple`. 467 468 Parameters 469 ---------- 470 \*constraints: 471 Constraint or logic operator objects. 472 473 Examples 474 -------- 475 .. code-block:: python 476 477 class CapitalAndSmall(IA5String): 478 ''' 479 ASN.1 specification: 480 481 CapitalAndSmall ::= 482 IA5String (FROM ("A".."Z"|"a".."z")) 483 ''' 484 subtypeSpec = ConstraintsIntersection( 485 PermittedAlphabetConstraint('A', 'Z'), 486 PermittedAlphabetConstraint('a', 'z') 487 ) 488 489 # this will succeed 490 capital_and_small = CapitalAndSmall('Hello') 491 492 # this will raise ValueConstraintError 493 capital_and_small = CapitalAndSmall('hello') 494 """ 495 def _testValue(self, value, idx): 496 for constraint in self._values: 497 constraint(value, idx) 498 499 500class ConstraintsUnion(AbstractConstraintSet): 501 """Create a ConstraintsUnion logic operator object. 502 503 The ConstraintsUnion logic operator only succeeds if 504 *at least a single* operand succeeds. 505 506 The ConstraintsUnion object can be applied to 507 any constraint and logic operator objects. 508 509 The ConstraintsUnion object duck-types the immutable 510 container object like Python :py:class:`tuple`. 511 512 Parameters 513 ---------- 514 \*constraints: 515 Constraint or logic operator objects. 516 517 Examples 518 -------- 519 .. code-block:: python 520 521 class CapitalOrSmall(IA5String): 522 ''' 523 ASN.1 specification: 524 525 CapitalOrSmall ::= 526 IA5String (FROM ("A".."Z") | FROM ("a".."z")) 527 ''' 528 subtypeSpec = ConstraintsIntersection( 529 PermittedAlphabetConstraint('A', 'Z'), 530 PermittedAlphabetConstraint('a', 'z') 531 ) 532 533 # this will succeed 534 capital_or_small = CapitalAndSmall('Hello') 535 536 # this will raise ValueConstraintError 537 capital_or_small = CapitalOrSmall('hello!') 538 """ 539 def _testValue(self, value, idx): 540 for constraint in self._values: 541 try: 542 constraint(value, idx) 543 except error.ValueConstraintError: 544 pass 545 else: 546 return 547 548 raise error.ValueConstraintError( 549 'all of %s failed for "%s"' % (self._values, value) 550 ) 551 552# TODO: 553# refactor InnerTypeConstraint 554# add tests for type check 555# implement other constraint types 556# make constraint validation easy to skip 557