summaryrefslogtreecommitdiff
path: root/dns/node.py
diff options
context:
space:
mode:
authorBob Halley <halley@dnspython.org>2021-12-02 14:44:08 -0800
committerBob Halley <halley@dnspython.org>2021-12-02 14:44:08 -0800
commita5cf8b0817d27abb0f27bfc6b8a78a518c534a35 (patch)
tree257d33cc48e9ca0972c98dddafe450b50ef4ba82 /dns/node.py
parent7ce649d1a1a4fe692c9896dc8b2287971b480314 (diff)
downloaddnspython-cname-and-other-data.tar.gz
refactor to have a get_node() in the txn APIcname-and-other-data
Diffstat (limited to 'dns/node.py')
-rw-r--r--dns/node.py123
1 files changed, 106 insertions, 17 deletions
diff --git a/dns/node.py b/dns/node.py
index 1739154..b8141c4 100644
--- a/dns/node.py
+++ b/dns/node.py
@@ -17,13 +17,53 @@
"""DNS nodes. A node is a set of rdatasets."""
+import enum
import io
+import dns.immutable
import dns.rdataset
import dns.rdatatype
import dns.renderer
+_cname_types = {
+ dns.rdatatype.CNAME,
+}
+
+# "neutral" types can coexist with a CNAME and thus are not "other data"
+_neutral_types = {
+ dns.rdatatype.NSEC, # RFC 4035 section 2.5
+ dns.rdatatype.NSEC3, # This is not likely to happen, but not impossible!
+ dns.rdatatype.KEY, # RFC 4035 section 2.5, RFC 3007
+}
+
+def _matches_type_or_its_signature(rdtypes, rdtype, covers):
+ return rdtype in rdtypes or \
+ (rdtype == dns.rdatatype.RRSIG and covers in rdtypes)
+
+
+@enum.unique
+class NodeKind(enum.Enum):
+ """Rdatasets in nodes
+ """
+ REGULAR = 0 # a.k.a "other data"
+ NEUTRAL = 1
+ CNAME = 2
+
+ @classmethod
+ def classify(cls, rdtype, covers):
+ if _matches_type_or_its_signature(_cname_types, rdtype, covers):
+ return NodeKind.CNAME
+ elif _matches_type_or_its_signature(_neutral_types, rdtype, covers):
+ return NodeKind.NEUTRAL
+ else:
+ return NodeKind.REGULAR
+
+ @classmethod
+ def classify_rdataset(cls, rdataset):
+ return cls.classify(rdataset.rdtype, rdataset.covers)
+
+
class Node:
"""A Node is a set of rdatasets.
@@ -95,22 +135,26 @@ class Node:
"""Append rdataset to the node with special handling for CNAME and
other data conditions.
- Specifically, if the rdataset being appended is a CNAME, then
- all rdatasets other than NSEC, NSEC3, and their covering RRSIGs
- are deleted. If the rdataset being appended is NOT a CNAME, then
- CNAME and RRSIG(CNAME) are deleted.
+ Specifically, if the rdataset being appended has ``NodeKind.CNAME``,
+ then all rdatasets other than KEY, NSEC, NSEC3, and their covering
+ RRSIGs are deleted. If the rdataset being appended has
+ ``NodeKind.REGUALAR`` then CNAME and RRSIG(CNAME) are deleted.
"""
# Make having just one rdataset at the node fast.
if len(self.rdatasets) > 0:
- if rdataset.implies_cname():
- self.rdatasets = [rds for rds in self.rdatasets
- if rds.ok_for_cname()]
- elif rdataset.implies_other_data():
- self.rdatasets = [rds for rds in self.rdatasets
- if rds.ok_for_other_data()]
+ kind = NodeKind.classify_rdataset(rdataset)
+ if kind == NodeKind.CNAME:
+ self.rdatasets = [rds for rds in self.rdatasets if
+ NodeKind.classify_rdataset(rds) !=
+ NodeKind.REGULAR]
+ elif kind == NodeKind.REGULAR:
+ self.rdatasets = [rds for rds in self.rdatasets if
+ NodeKind.classify_rdataset(rds) !=
+ NodeKind.CNAME]
+ # Otherwise the rdataset is NodeKind.NEUTRAL and we do not need to
+ # edit self.rdatasets.
self.rdatasets.append(rdataset)
-
def find_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE,
create=False):
"""Find an rdataset matching the specified properties in the
@@ -221,11 +265,56 @@ class Node:
replacement.covers)
self._append_rdataset(replacement)
- def is_cname(self):
- """Is this a CNAME node?
+ def classify(self):
+ """Classify a node.
+
+ A node which contains a CNAME or RRSIG(CNAME) is a
+ ``NodeKind.CNAME`` node.
- If the node has a CNAME or an RRSIG(CNAME) it is considered a CNAME
- node for CNAME-and-other-data purposes, and ``True`` is returned.
- Otherwise the node is an "other data" node, and ``False`` is returned.
+ A node which contains only "neutral" types, i.e. types allowed to
+ co-exist with a CNAME, is a ``NodeKind.NEUTRAL`` node. The neutral
+ types are NSEC, NSEC3, KEY, and their associated RRSIGS. An empty node
+ is also considered neutral.
+
+ A node which contains some rdataset which is not a CNAME, RRSIG(CNAME),
+ or a neutral type is a a ``NodeKind.REGULAR`` node. Regular nodes are
+ also commonly referred to as "other data".
"""
- return any(rdataset.implies_cname() for rdataset in self.rdatasets)
+ for rdataset in self.rdatasets:
+ kind = NodeKind.classify(rdataset.rdtype, rdataset.covers)
+ if kind != NodeKind.NEUTRAL:
+ return kind
+ return NodeKind.NEUTRAL
+
+ def is_immutable(self):
+ return False
+
+
+dns.immutable.immutable
+class ImmutableNode(Node):
+ def __init__(self, node):
+ super().__init__()
+ self.rdatasets = tuple(
+ [dns.rdataset.ImmutableRdataset(rds) for rds in node.rdatasets]
+ )
+
+ def find_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE,
+ create=False):
+ if create:
+ raise TypeError("immutable")
+ return super().find_rdataset(rdclass, rdtype, covers, False)
+
+ def get_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE,
+ create=False):
+ if create:
+ raise TypeError("immutable")
+ return super().get_rdataset(rdclass, rdtype, covers, False)
+
+ def delete_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE):
+ raise TypeError("immutable")
+
+ def replace_rdataset(self, replacement):
+ raise TypeError("immutable")
+
+ def is_immutable(self):
+ return True