Closes #1264.
dns.flags.to_text() and dns.flags.edns_to_text() iterated only the named
enum members, so any set bit without a named flag (for example the EDNS
0x2000 bit) was silently dropped from the text representation. Such bits
are now rendered as FLAGn, where n is the bit position, and from_text()
and edns_from_text() parse that form back, so the conversions round-trip.
Adds tests in tests/test_flags.py and a whatsnew entry.
Fixes [#1264].
Co-authored-by: Eugen Goebel <eugen-goebel@users.noreply.github.com>
flags = 0
tokens = text.split()
for t in tokens:
- flags |= enum_class[t.upper()]
+ token = t.upper()
+ if token.startswith("FLAG") and token[4:].isdigit():
+ # An unnamed flag, rendered by _to_text() as FLAGn (see below).
+ flags |= 1 << int(token[4:])
+ else:
+ flags |= enum_class[token]
return flags
def _to_text(flags: int, enum_class: Any) -> str:
text_flags = []
+ known = 0
for k, v in enum_class.__members__.items():
+ known |= int(v)
if flags & v != 0:
text_flags.append(k)
+ # Render any set bits that do not have a named flag as FLAGn, where n is
+ # the bit position, so that they are not silently dropped. These tokens
+ # round-trip through _from_text().
+ unknown = flags & ~known
+ bit = 0
+ while unknown:
+ if unknown & 1:
+ text_flags.append(f"FLAG{bit}")
+ unknown >>= 1
+ bit += 1
return " ".join(text_flags)
only Python 3" update, also quite some time ago. It is now renamed to "type"
(again) so it matches the Python 3 code it is overriding.
+* dns.flags.to_text() and dns.flags.edns_to_text() no longer silently drop set
+ bits that have no named flag. Such bits are now rendered as ``FLAGn``, where n
+ is the bit position, and dns.flags.from_text() / edns_from_text() parse that form
+ back, so the conversions round-trip. See issue #1264.
+
2.8.0
-----
flags = dns.flags.QR | dns.flags.AA | dns.flags.RD | dns.flags.RA
self.assertEqual(dns.flags.to_text(flags), "QR AA RD RA")
+ def test_to_text_unknown_flag(self):
+ # An unknown flag bit must not be silently dropped (issue #1264).
+ self.assertEqual(dns.flags.to_text(0x8000 | 0x0001), "QR FLAG0")
+
+ def test_edns_to_text_unknown_flag(self):
+ # EDNS flags behave the same: the unnamed 0x2000 bit is rendered.
+ self.assertEqual(dns.flags.edns_to_text(0x2000), "FLAG13")
+ self.assertEqual(dns.flags.edns_to_text(0x8000 | 0x2000), "DO FLAG13")
+
+ def test_unknown_flag_roundtrip(self):
+ for value in [0x0001, 0x8000 | 0x0001, 0x0400 | 0x0008]:
+ self.assertEqual(dns.flags.from_text(dns.flags.to_text(value)), value)
+ for value in [0x2000, 0x8000 | 0x2000, 0x4000 | 0x2000]:
+ self.assertEqual(
+ dns.flags.edns_from_text(dns.flags.edns_to_text(value)), value
+ )
+
def test_rcode_badvers(self):
rcode = dns.rcode.BADVERS
self.assertEqual(rcode.value, 16)