00001 """Classes dealing with sets of resources.
00002
00003 The basic mechanics of a resource set are the same for any of the
00004 resources we handle (ASNs, IPv4 addresses, or IPv6 addresses), so we
00005 can provide the same operations on any of them, even though the
00006 underlying details vary.
00007
00008 We also provide some basic set operations (union, intersection, etc).
00009
00010 $Id: resource_set.py 1984 2008-07-08 08:00:36Z sra $
00011
00012 Copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
00013
00014 Permission to use, copy, modify, and distribute this software for any
00015 purpose with or without fee is hereby granted, provided that the above
00016 copyright notice and this permission notice appear in all copies.
00017
00018 THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
00019 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00020 AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
00021 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00022 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00023 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00024 PERFORMANCE OF THIS SOFTWARE.
00025 """
00026
00027 import re
00028 import rpki.ipaddrs, rpki.oids, rpki.exceptions
00029
00030
00031
00032
00033 inherit_token = "<inherit>"
00034
00035 class resource_range(object):
00036 """Generic resource range type. Assumes underlying type is some
00037 kind of integer.
00038
00039 This is a virtual class. You probably don't want to use this type
00040 directly.
00041 """
00042
00043 def __init__(self, min, max):
00044 """Initialize and sanity check a resource_range."""
00045 assert min <= max, "Mis-ordered range: %s before %s" % (str(min), str(max))
00046 self.min = min
00047 self.max = max
00048
00049 def __cmp__(self, other):
00050 """Compare two resource_range objects."""
00051 assert self.__class__ is other.__class__
00052 c = self.min - other.min
00053 if c == 0: c = self.max - other.max
00054 if c < 0: c = -1
00055 if c > 0: c = 1
00056 return c
00057
00058 class resource_range_as(resource_range):
00059 """Range of Autonomous System Numbers.
00060
00061 Denotes a single ASN by a range whose min and max values are identical.
00062 """
00063
00064
00065
00066
00067 datum_type = long
00068
00069 def __str__(self):
00070 """Convert a resource_range_as to string format."""
00071 if self.min == self.max:
00072 return str(self.min)
00073 else:
00074 return str(self.min) + "-" + str(self.max)
00075
00076 def to_rfc3779_tuple(self):
00077 """Convert a resource_range_as to tuple format for RFC 3779 ASN.1 encoding."""
00078 if self.min == self.max:
00079 return ("id", self.min)
00080 else:
00081 return ("range", (self.min, self.max))
00082
00083 class resource_range_ip(resource_range):
00084 """Range of (generic) IP addresses.
00085
00086 Prefixes are converted to ranges on input, and ranges that can be
00087 represented as prefixes are written as prefixes on output.
00088
00089 This is a virtual class. You probably don't want to use it directly.
00090 """
00091
00092 def _prefixlen(self):
00093 """Determine whether a resource_range_ip can be expressed as a prefix."""
00094 mask = self.min ^ self.max
00095 if self.min & mask != 0:
00096 return -1
00097 prefixlen = self.datum_type.bits
00098 while mask & 1:
00099 prefixlen -= 1
00100 mask >>= 1
00101 if mask:
00102 return -1
00103 else:
00104 return prefixlen
00105
00106 def __str__(self):
00107 """Convert a resource_range_ip to string format."""
00108 prefixlen = self._prefixlen()
00109 if prefixlen < 0:
00110 return str(self.min) + "-" + str(self.max)
00111 else:
00112 return str(self.min) + "/" + str(prefixlen)
00113
00114 def to_rfc3779_tuple(self):
00115 """Convert a resource_range_ip to tuple format for RFC 3779 ASN.1 encoding."""
00116 prefixlen = self._prefixlen()
00117 if prefixlen < 0:
00118 return ("addressRange", (_long2bs(self.min, self.datum_type.bits, strip = 0),
00119 _long2bs(self.max, self.datum_type.bits, strip = 1)))
00120 else:
00121 return ("addressPrefix", _long2bs(self.min, self.datum_type.bits, prefixlen = prefixlen))
00122
00123 @classmethod
00124 def make_prefix(cls, address, prefixlen):
00125 """Construct a resource range corresponding to a prefix."""
00126 assert isinstance(address, cls.datum_type) and isinstance(prefixlen, (int, long))
00127 assert prefixlen >= 0 and prefixlen <= cls.datum_type.bits, "Nonsensical prefix length: %s" % prefixlen
00128 mask = (1 << (cls.datum_type.bits - prefixlen)) - 1
00129 assert (address & mask) == 0, "Resource not in canonical form: %s/s" % (address, prefixlen)
00130 return cls(cls.datum_type(address), cls.datum_type(address | mask))
00131
00132 class resource_range_ipv4(resource_range_ip):
00133 """Range of IPv4 addresses."""
00134
00135
00136
00137
00138 datum_type = rpki.ipaddrs.v4addr
00139
00140 class resource_range_ipv6(resource_range_ip):
00141 """Range of IPv6 addresses."""
00142
00143
00144
00145
00146 datum_type = rpki.ipaddrs.v6addr
00147
00148 def _rsplit(rset, that):
00149 """Utility function to split a resource range into two resource ranges."""
00150 this = rset.pop(0)
00151 cell_type = type(this.min)
00152 assert type(this) is type(that) and type(this.max) is cell_type and \
00153 type(that.min) is cell_type and type(that.max) is cell_type
00154 if this.min < that.min:
00155 rset.insert(0, type(this)(this.min, cell_type(that.min - 1)))
00156 rset.insert(1, type(this)(that.min, this.max))
00157 else:
00158 assert this.max > that.max
00159 rset.insert(0, type(this)(this.min, that.max))
00160 rset.insert(1, type(this)(cell_type(that.max + 1), this.max))
00161
00162 class resource_set(list):
00163 """Generic resource set.
00164 This is a list subclass containing resource ranges.
00165
00166 This is a virtual class. You probably don't want to use it
00167 directly.
00168 """
00169
00170
00171
00172
00173 inherit = False
00174
00175 def __init__(self, ini = None):
00176 """Initialize a resource_set."""
00177 if isinstance(ini, (int, long)):
00178 ini = str(ini)
00179 if ini == inherit_token:
00180 self.inherit = True
00181 elif isinstance(ini, str) and len(ini):
00182 self.extend(self.parse_str(s) for s in ini.split(","))
00183 elif isinstance(ini, tuple):
00184 self.parse_rfc3779_tuple(ini)
00185 elif isinstance(ini, list):
00186 self.extend(ini)
00187 else:
00188 assert ini is None or ini == "", "Unexpected initializer: %s" % str(ini)
00189 assert not self.inherit or not self
00190 self.sort()
00191 for i in xrange(len(self) - 2, -1, -1):
00192 if self[i].max + 1 == self[i+1].min:
00193 self[i] = type(self[i])(self[i].min, self[i+1].max)
00194 self.pop(i + 1)
00195 if __debug__:
00196 for i in xrange(0, len(self) - 1):
00197 assert self[i].max < self[i+1].min, "Resource overlap: %s %s" % (self[i], self[i+1])
00198
00199 def __str__(self):
00200 """Convert a resource_set to string format."""
00201 if self.inherit:
00202 return inherit_token
00203 else:
00204 return ",".join(str(x) for x in self)
00205
00206 def _comm(self, other):
00207 """Like comm(1), sort of.
00208
00209 Returns a tuple of three resource sets: resources only in self,
00210 resources only in other, and resources in both. Used (not very
00211 efficiently) as the basis for most set operations on resource
00212 sets.
00213 """
00214 assert not self.inherit
00215 assert type(self) is type(other), "Type mismatch %s %s" % (repr(type(self)), repr(type(other)))
00216 set1 = self[:]
00217 set2 = other[:]
00218 only1, only2, both = [], [], []
00219 while set1 or set2:
00220 if set1 and (not set2 or set1[0].max < set2[0].min):
00221 only1.append(set1.pop(0))
00222 elif set2 and (not set1 or set2[0].max < set1[0].min):
00223 only2.append(set2.pop(0))
00224 elif set1[0].min < set2[0].min:
00225 _rsplit(set1, set2[0])
00226 elif set2[0].min < set1[0].min:
00227 _rsplit(set2, set1[0])
00228 elif set1[0].max < set2[0].max:
00229 _rsplit(set2, set1[0])
00230 elif set2[0].max < set1[0].max:
00231 _rsplit(set1, set2[0])
00232 else:
00233 assert set1[0].min == set2[0].min and set1[0].max == set2[0].max
00234 both.append(set1.pop(0))
00235 set2.pop(0)
00236 return type(self)(only1), type(self)(only2), type(self)(both)
00237
00238 def union(self, other):
00239 """Set union for resource sets."""
00240 assert not self.inherit
00241 assert type(self) is type(other), "Type mismatch: %s %s" % (repr(type(self)), repr(type(other)))
00242 set1 = self[:]
00243 set2 = other[:]
00244 result = []
00245 while set1 or set2:
00246 if set1 and (not set2 or set1[0].max < set2[0].min):
00247 result.append(set1.pop(0))
00248 elif set2 and (not set1 or set2[0].max < set1[0].min):
00249 result.append(set2.pop(0))
00250 else:
00251 this = set1.pop(0)
00252 that = set2.pop(0)
00253 assert type(this) is type(that)
00254 if this.min < that.min: min = this.min
00255 else: min = that.min
00256 if this.max > that.max: max = this.max
00257 else: max = that.max
00258 result.append(type(this)(min, max))
00259 return type(self)(result)
00260
00261 def intersection(self, other):
00262 """Set intersection for resource sets."""
00263 return self._comm(other)[2]
00264
00265 def difference(self, other):
00266 """Set difference for resource sets."""
00267 return self._comm(other)[0]
00268
00269 def symmetric_difference(self, other):
00270 """Set symmetric difference (XOR) for resource sets."""
00271 com = self._comm(other)
00272 return com[0].union(com[1])
00273
00274 def contains(self, item):
00275 """Set membership test for resource sets."""
00276 assert not self.inherit
00277 for i in self:
00278 if isinstance(item, type(i)) and i.min <= item.min and i.max >= item.max:
00279 return True
00280 elif isinstance(item, type(i.min)) and i.min <= item and i.max >= item:
00281 return True
00282 else:
00283 assert isinstance(item, (type(i), type(i.min)))
00284 return False
00285
00286 def issubset(self, other):
00287 """Test whether self is a subset (possibly improper) of other."""
00288 for i in self:
00289 if not other.contains(i):
00290 return False
00291 return True
00292
00293 def issuperset(self, other):
00294 """Test whether self is a superset (possibly improper) of other."""
00295 return other.issubset(self)
00296
00297 @classmethod
00298 def from_sql(cls, sql, query, args = None):
00299 """Create resource set from an SQL query.
00300
00301 sql is an object that supports execute() and fetchall() methods
00302 like a DB API 2.0 cursor object.
00303
00304 query is an SQL query that returns a sequence of (min, max) pairs.
00305 """
00306
00307 sql.execute(query, args)
00308 return cls(ini = [cls.range_type(cls.range_type.datum_type(b),
00309 cls.range_type.datum_type(e))
00310 for (b,e) in sql.fetchall()])
00311
00312 class resource_set_as(resource_set):
00313 """ASN resource set."""
00314
00315
00316
00317
00318 range_type = resource_range_as
00319
00320 def parse_str(self, x):
00321 """Parse ASN resource sets from text (eg, XML attributes)."""
00322 r = re.match("^([0-9]+)-([0-9]+)$", x)
00323 if r:
00324 return resource_range_as(long(r.group(1)), long(r.group(2)))
00325 else:
00326 return resource_range_as(long(x), long(x))
00327
00328 def parse_rfc3779_tuple(self, x):
00329 """Parse ASN resource from tuple format generated by RFC 3779 ASN.1 decoder."""
00330 if x[0] == "asIdsOrRanges":
00331 for aor in x[1]:
00332 if aor[0] == "range":
00333 min = aor[1][0]
00334 max = aor[1][1]
00335 else:
00336 min = aor[1]
00337 max = min
00338 self.append(resource_range_as(min, max))
00339 else:
00340 assert x[0] == "inherit"
00341 self.inherit = True
00342
00343 def to_rfc3779_tuple(self):
00344 """Convert ASN resource set into tuple format used for RFC 3779 ASN.1 encoding."""
00345 if self:
00346 return ("asIdsOrRanges", tuple(a.to_rfc3779_tuple() for a in self))
00347 elif self.inherit:
00348 return ("inherit", "")
00349 else:
00350 return None
00351
00352 class resource_set_ip(resource_set):
00353 """(Generic) IP address resource set.
00354
00355 This is a virtual class. You probably don't want to use it
00356 directly.
00357 """
00358
00359 def parse_str(self, x):
00360 """Parse IP address resource sets from text (eg, XML attributes)."""
00361 r = re.match("^([0-9:.a-fA-F]+)-([0-9:.a-fA-F]+)$", x)
00362 if r:
00363 return self.range_type(self.range_type.datum_type(r.group(1)), self.range_type.datum_type(r.group(2)))
00364 r = re.match("^([0-9:.a-fA-F]+)/([0-9]+)$", x)
00365 if r:
00366 return self.range_type.make_prefix(self.range_type.datum_type(r.group(1)), int(r.group(2)))
00367 raise RuntimeError, 'Bad IP resource "%s"' % (x)
00368
00369 def parse_rfc3779_tuple(self, x):
00370 """Parse IP address resource sets from tuple format generated by RFC 3779 ASN.1 decoder."""
00371 if x[0] == "addressesOrRanges":
00372 for aor in x[1]:
00373 if aor[0] == "addressRange":
00374 min = _bs2long(aor[1][0], self.range_type.datum_type.bits, 0)
00375 max = _bs2long(aor[1][1], self.range_type.datum_type.bits, 1)
00376 else:
00377 min = _bs2long(aor[1], self.range_type.datum_type.bits, 0)
00378 max = _bs2long(aor[1], self.range_type.datum_type.bits, 1)
00379 self.append(self.range_type(self.range_type.datum_type(min), self.range_type.datum_type(max)))
00380 else:
00381 assert x[0] == "inherit"
00382 self.inherit = True
00383
00384 def to_rfc3779_tuple(self):
00385 """Convert IP resource set into tuple format used by RFC 3779 ASN.1 encoder."""
00386 if self:
00387 return (self.afi, ("addressesOrRanges", tuple(a.to_rfc3779_tuple() for a in self)))
00388 elif self.inherit:
00389 return (self.afi, ("inherit", ""))
00390 else:
00391 return None
00392
00393 class resource_set_ipv4(resource_set_ip):
00394 """IPv4 address resource set."""
00395
00396
00397
00398
00399 range_type = resource_range_ipv4
00400
00401
00402
00403
00404 afi = "\x00\x01"
00405
00406 class resource_set_ipv6(resource_set_ip):
00407 """IPv6 address resource set."""
00408
00409
00410
00411
00412 range_type = resource_range_ipv6
00413
00414
00415
00416
00417 afi = "\x00\x02"
00418
00419 def _bs2long(bs, addrlen, fill):
00420 """Utility function to convert a bitstring (POW.pkix tuple
00421 representation) into a Python long.
00422 """
00423 x = 0L
00424 for y in bs:
00425 x = (x << 1) | y
00426 for y in xrange(addrlen - len(bs)):
00427 x = (x << 1) | fill
00428 return x
00429
00430 def _long2bs(number, addrlen, prefixlen = None, strip = None):
00431 """Utility function to convert a Python long into a POW.pkix tuple
00432 bitstring. This is a bit complicated because it supports the
00433 fiendishly compact encoding used in RFC 3779.
00434 """
00435 assert prefixlen is None or strip is None
00436 bs = []
00437 while number:
00438 bs.append(int(number & 1))
00439 number >>= 1
00440 if addrlen > len(bs):
00441 bs.extend((0 for i in xrange(addrlen - len(bs))))
00442 bs.reverse()
00443 if prefixlen is not None:
00444 return tuple(bs[0:prefixlen])
00445 if strip is not None:
00446 while bs and bs[-1] == strip:
00447 bs.pop()
00448 return tuple(bs)
00449
00450 class resource_bag(object):
00451 """Container to simplify passing around the usual triple of ASN,
00452 IPv4, and IPv6 resource sets.
00453 """
00454
00455
00456
00457
00458
00459
00460
00461
00462
00463
00464
00465
00466
00467 def __init__(self, asn = None, v4 = None, v6 = None, valid_until = None):
00468 self.asn = asn or resource_set_as()
00469 self.v4 = v4 or resource_set_ipv4()
00470 self.v6 = v6 or resource_set_ipv6()
00471 self.valid_until = valid_until
00472
00473 def oversized(self, other):
00474 """True iff self is oversized with respect to other."""
00475 return not self.asn.issubset(other.asn) or \
00476 not self.v4.issubset(other.v4) or \
00477 not self.v6.issubset(other.v6)
00478
00479 def undersized(self, other):
00480 """True iff self is undersized with respect to other."""
00481 return not other.asn.issubset(self.asn) or \
00482 not other.v4.issubset(self.v4) or \
00483 not other.v6.issubset(self.v6)
00484
00485 @classmethod
00486 def from_rfc3779_tuples(cls, exts):
00487 """Build a resource_bag from intermediate form generated by RFC 3779 ASN.1 decoder."""
00488 asn = None
00489 v4 = None
00490 v6 = None
00491 for x in exts:
00492 if x[0] == rpki.oids.name2oid["sbgp-autonomousSysNum"]:
00493 assert len(x[2]) == 1 or x[2][1] is None, "RDI not implemented: %s" % (str(x))
00494 assert asn is None
00495 asn = resource_set_as(x[2][0])
00496 if x[0] == rpki.oids.name2oid["sbgp-ipAddrBlock"]:
00497 for fam in x[2]:
00498 if fam[0] == resource_set_ipv4.afi:
00499 assert v4 is None
00500 v4 = resource_set_ipv4(fam[1])
00501 if fam[0] == resource_set_ipv6.afi:
00502 assert v6 is None
00503 v6 = resource_set_ipv6(fam[1])
00504 return cls(asn, v4, v6)
00505
00506 def empty(self):
00507 """Return True iff all resource sets in this bag are empty."""
00508 return not self.asn and not self.v4 and not self.v6
00509
00510 def __eq__(self, other):
00511 return self.asn == other.asn and \
00512 self.v4 == other.v4 and \
00513 self.v6 == other.v6 and \
00514 self.valid_until == other.valid_until
00515
00516 def __ne__(self, other):
00517 return not (self == other)
00518
00519 def intersection(self, other):
00520 """Compute intersection with another resource_bag.
00521 valid_until attribute (if any) inherits from self.
00522 """
00523 return self.__class__(self.asn.intersection(other.asn),
00524 self.v4.intersection(other.v4),
00525 self.v6.intersection(other.v6),
00526 self.valid_until)
00527
00528 def union(self, other):
00529 """Compute union with another resource_bag.
00530 valid_until attribute (if any) inherits from self.
00531 """
00532 return self.__class__(self.asn.union(other.asn),
00533 self.v4.union(other.v4),
00534 self.v6.union(other.v6),
00535 self.valid_until)
00536
00537 def __str__(self):
00538 s = ""
00539 if self.asn:
00540 s += "ASN: %s" % self.asn
00541 if self.v4:
00542 if s:
00543 s += ", "
00544 s += "V4: %s" % self.v4
00545 if self.v6:
00546 if s:
00547 s += ", "
00548 s += "V6: %s" % self.v6
00549 return s
00550
00551
00552
00553
00554
00555
00556
00557
00558
00559 class roa_prefix(object):
00560 """ROA prefix. This is similar to the resource_range_ip class, but
00561 differs in that it only represents prefixes, never ranges, and
00562 includes the maximum prefix length as an additional value.
00563
00564 This is a virtual class, you probably don't want to use it directly.
00565 """
00566
00567
00568
00569
00570
00571
00572
00573
00574
00575
00576 def __init__(self, address, prefixlen, max_prefixlen = None):
00577 """Initialize a ROA prefix. max_prefixlen is optional and
00578 defaults to prefixlen. max_prefixlen must not be smaller than
00579 prefixlen.
00580 """
00581 if max_prefixlen is None:
00582 max_prefixlen = prefixlen
00583 assert max_prefixlen >= prefixlen, "Bad max_prefixlen: %d must not be shorter than %d" % (max_prefixlen, prefixlen)
00584 self.address = address
00585 self.prefixlen = prefixlen
00586 self.max_prefixlen = max_prefixlen
00587
00588 def __cmp__(self, other):
00589 """Compare two ROA prefix objects. Comparision is based on
00590 address, prefixlen, and max_prefixlen, in that order.
00591 """
00592 assert self.__class__ is other.__class__
00593 c = self.address - other.address
00594 if c == 0: c = self.prefixlen - other.prefixlen
00595 if c == 0: c = self.max_prefixlen - other.max_prefixlen
00596 if c < 0: c = -1
00597 if c > 0: c = 1
00598 return c
00599
00600 def __str__(self):
00601 """Convert a ROA prefix to string format."""
00602 if self.prefixlen == self.max_prefixlen:
00603 return str(self.address) + "/" + str(self.prefixlen)
00604 else:
00605 return str(self.address) + "/" + str(self.prefixlen) + "-" + str(self.max_prefixlen)
00606
00607 def to_resource_range(self):
00608 """Convert this ROA prefix to the equivilent resource_range_ip
00609 object. This is an irreversable transformation because it loses
00610 the max_prefixlen attribute, nothing we can do about that.
00611 """
00612 return self.range_type.make_prefix(self.address, self.prefixlen)
00613
00614 def min(self):
00615 """Return lowest address covered by prefix."""
00616 return self.address
00617
00618 def max(self):
00619 """Return highest address covered by prefix."""
00620 t = self.range_type.datum_type
00621 return t(self.address | ((1 << (t.bits - self.prefixlen)) - 1))
00622
00623 def to_roa_tuple(self):
00624 """Convert a resource_range_ip to tuple format for ROA ASN.1 encoding."""
00625 return (_long2bs(self.address, self.range_type.datum_type.bits, prefixlen = self.prefixlen),
00626 None if self.prefixlen == self.max_prefixlen else self.max_prefixlen)
00627
00628 class roa_prefix_ipv4(roa_prefix):
00629 """IPv4 ROA prefix."""
00630
00631
00632
00633
00634 range_type = resource_range_ipv4
00635
00636 class roa_prefix_ipv6(roa_prefix):
00637 """IPv6 ROA prefix."""
00638
00639
00640
00641
00642 range_type = resource_range_ipv6
00643
00644 class roa_prefix_set(list):
00645 """Set of ROA prefixes, analogous to the resource_set_ip class."""
00646
00647 def __init__(self, ini = None):
00648 """Initialize a ROA prefix set."""
00649 if isinstance(ini, str) and len(ini):
00650 self.extend(self.parse_str(s) for s in ini.split(","))
00651 elif isinstance(ini, (list, tuple)):
00652 self.extend(ini)
00653 else:
00654 assert ini is None or ini == "", "Unexpected initializer: %s" % str(ini)
00655 self.sort()
00656 if __debug__:
00657 for i in xrange(0, len(self) - 1):
00658 assert self[i].max() < self[i+1].min(), "Prefix overlap: %s %s" % (self[i], self[i+1])
00659
00660 def __str__(self):
00661 """Convert a ROA prefix set to string format."""
00662 return ",".join(str(x) for x in self)
00663
00664 def parse_str(self, x):
00665 """Parse ROA prefix from text (eg, an XML attribute)."""
00666 r = re.match("^([0-9:.a-fA-F]+)/([0-9]+)-([0-9]+)$", x)
00667 if r:
00668 return self.prefix_type(self.prefix_type.range_type.datum_type(r.group(1)), int(r.group(2)), int(r.group(3)))
00669 r = re.match("^([0-9:.a-fA-F]+)/([0-9]+)$", x)
00670 if r:
00671 return self.prefix_type(self.prefix_type.range_type.datum_type(r.group(1)), int(r.group(2)))
00672 raise RuntimeError, 'Bad ROA prefix "%s"' % (x)
00673
00674 def to_resource_set(self):
00675 """Convert a ROA prefix set to a resource set. This is an
00676 irreversable transformation.
00677 """
00678 return self.resource_set_type([p.to_resource_range() for p in self])
00679
00680 @classmethod
00681 def from_sql(cls, sql, query, args = None):
00682 """Create ROA prefix set from an SQL query.
00683
00684 sql is an object that supports execute() and fetchall() methods
00685 like a DB API 2.0 cursor object.
00686
00687 query is an SQL query that returns a sequence of (address,
00688 prefixlen, max_prefixlen) triples.
00689 """
00690
00691 sql.execute(query, args)
00692 return cls([cls.prefix_type(cls.prefix_type.range_type.datum_type(x), int(y), int(z))
00693 for (x,y,z) in sql.fetchall()])
00694
00695 def to_roa_tuple(self):
00696 """Convert ROA prefix set into tuple format used by ROA ASN.1 encoder.
00697 This is a variation on the format used in RFC 3779."""
00698 if self:
00699 return (self.resource_set_type.afi, tuple(a.to_roa_tuple() for a in self))
00700 else:
00701 return None
00702
00703 class roa_prefix_set_ipv4(roa_prefix_set):
00704 """Set of IPv4 ROA prefixes."""
00705
00706
00707
00708
00709 prefix_type = roa_prefix_ipv4
00710
00711
00712
00713
00714 resource_set_type = resource_set_ipv4
00715
00716 class roa_prefix_set_ipv6(roa_prefix_set):
00717 """Set of IPv6 ROA prefixes."""
00718
00719
00720
00721
00722 prefix_type = roa_prefix_ipv6
00723
00724
00725
00726
00727 resource_set_type = resource_set_ipv6
00728
00729
00730
00731 if __name__ == "__main__":
00732
00733 def test1(t, s1, s2):
00734 if isinstance(s1, str) and isinstance(s2, str):
00735 print "x: ", s1
00736 print "y: ", s2
00737 r1 = t(s1)
00738 r2 = t(s2)
00739 print "x: ", r1
00740 print "y: ", r2
00741 v1 = r1._comm(r2)
00742 v2 = r2._comm(r1)
00743 assert v1[0] == v2[1] and v1[1] == v2[0] and v1[2] == v2[2]
00744 for i in r1: assert r1.contains(i) and r1.contains(i.min) and r1.contains(i.max)
00745 for i in r2: assert r2.contains(i) and r2.contains(i.min) and r2.contains(i.max)
00746 for i in v1[0]: assert r1.contains(i) and not r2.contains(i)
00747 for i in v1[1]: assert not r1.contains(i) and r2.contains(i)
00748 for i in v1[2]: assert r1.contains(i) and r2.contains(i)
00749 v1 = r1.union(r2)
00750 v2 = r2.union(r1)
00751 assert v1 == v2
00752 print "x|y:", v1
00753 v1 = r1.difference(r2)
00754 v2 = r2.difference(r1)
00755 print "x-y:", v1
00756 print "y-x:", v2
00757 v1 = r1.symmetric_difference(r2)
00758 v2 = r2.symmetric_difference(r1)
00759 assert v1 == v2
00760 print "x^y:", v1
00761 v1 = r1.intersection(r2)
00762 v2 = r2.intersection(r1)
00763 assert v1 == v2
00764 print "x&y:", v1
00765
00766 def test2(t, s1, s2):
00767 print "x: ", s1
00768 print "y: ", s2
00769 r1 = t(s1)
00770 r2 = t(s2)
00771 print "x: ", r1
00772 print "y: ", r2
00773 print "x>y:", (r1 > r2)
00774 print "x<y:", (r1 < r2)
00775 test1(t.resource_set_type, r1.to_resource_set(), r2.to_resource_set())
00776
00777 print
00778 print "Testing set operations on resource sets"
00779 print
00780 test1(resource_set_as, "1,2,3,4,5,6,11,12,13,14,15", "1,2,3,4,5,6,111,121,131,141,151")
00781 print
00782 test1(resource_set_ipv4, "10.0.0.44/32,10.6.0.2/32", "10.3.0.0/24,10.0.0.77/32")
00783 print
00784 test1(resource_set_ipv4, "10.0.0.44/32,10.6.0.2/32", "10.0.0.0/24")
00785 print
00786 test1(resource_set_ipv4, "10.0.0.0/24", "10.3.0.0/24,10.0.0.77/32")
00787 print
00788 print "Testing set operations on ROA prefixes"
00789 print
00790 test2(roa_prefix_set_ipv4, "10.0.0.44/32,10.6.0.2/32", "10.3.0.0/24,10.0.0.77/32")
00791 print
00792 test2(roa_prefix_set_ipv4, "10.0.0.0/24-32,10.6.0.0/24-32", "10.3.0.0/24,10.0.0.0/16-32")
00793 print
00794 test2(roa_prefix_set_ipv4, "10.3.0.0/24-24,10.0.0.0/16-32", "10.3.0.0/24,10.0.0.0/16-32")
00795 print