|
12 | 12 | from ._exceptions import AbstractMethodException |
13 | 13 | from ._utils.net import _is_v6_address |
14 | 14 | from ._utils.time import current_time_millis |
15 | | -from .const import _CLASS_MASK, _CLASS_UNIQUE, _TYPE_ANY |
| 15 | +from .const import _CLASS_MASK, _CLASS_UNIQUE, _CLASSES, _TYPE_ANY, _TYPES |
16 | 16 |
|
17 | 17 | _LEN_BYTE = 1 |
18 | 18 | _LEN_SHORT = 2 |
|
28 | 28 | _float = float |
29 | 29 | _int = int |
30 | 30 |
|
| 31 | + |
| 32 | +def _type_label(type_: int) -> str: |
| 33 | + return _TYPES.get(type_, f"unknown-type-{type_}") |
| 34 | + |
| 35 | + |
| 36 | +def _class_label(class_: int) -> str: |
| 37 | + return _CLASSES.get(class_, f"unknown-class-{class_}") |
| 38 | + |
| 39 | + |
| 40 | +def _format_display(kind: str, fields: list[tuple[str, str]]) -> str: |
| 41 | + body = " ".join([f"{key}={value}" if key else value for key, value in fields]) |
| 42 | + return f"<{kind} {body}>" |
| 43 | + |
| 44 | + |
31 | 45 | if TYPE_CHECKING: |
32 | 46 | from ._protocol.incoming import DNSIncoming |
33 | 47 | from ._protocol.outgoing import DNSOutgoing |
@@ -58,6 +72,39 @@ def __eq__(self, other: Any) -> bool: |
58 | 72 | """Equal when key, type and class match.""" |
59 | 73 | return isinstance(other, DNSEntry) and self._dns_entry_matches(other) |
60 | 74 |
|
| 75 | + @property |
| 76 | + def type_label(self) -> str: |
| 77 | + """Human readable label for the record type.""" |
| 78 | + return _type_label(self.type) |
| 79 | + |
| 80 | + @property |
| 81 | + def class_label(self) -> str: |
| 82 | + """Human readable label for the record class.""" |
| 83 | + return _class_label(self.class_) |
| 84 | + |
| 85 | + def _display_fields(self) -> list[tuple[str, str]]: |
| 86 | + fields = [("name", self.name), ("type", self.type_label), ("class", self.class_label)] |
| 87 | + if self.unique: |
| 88 | + fields.append(("", "unique")) |
| 89 | + return fields |
| 90 | + |
| 91 | + @staticmethod |
| 92 | + def get_class_(class_: int) -> str: |
| 93 | + """Compatibility alias for the class_label property.""" |
| 94 | + return _class_label(class_) |
| 95 | + |
| 96 | + @staticmethod |
| 97 | + def get_type(t: int) -> str: |
| 98 | + """Compatibility alias for the type_label property.""" |
| 99 | + return _type_label(t) |
| 100 | + |
| 101 | + def entry_to_string(self, hdr: str, other: bytes | str | None) -> str: |
| 102 | + """Compatibility alias rendering through the display formatter.""" |
| 103 | + fields = self._display_fields() |
| 104 | + if other is not None: |
| 105 | + fields.append(("data", str(other))) |
| 106 | + return _format_display(hdr, fields) |
| 107 | + |
61 | 108 | def _dns_entry_matches(self, other: DNSEntry) -> bool: |
62 | 109 | return self.key == other.key and self.type == other.type and self.class_ == other.class_ |
63 | 110 |
|
@@ -86,12 +133,8 @@ def __hash__(self) -> int: |
86 | 133 | return self._hash |
87 | 134 |
|
88 | 135 | def __repr__(self) -> str: |
89 | | - return "{}[question,{},{},{}]".format( |
90 | | - self.get_type(self.type), |
91 | | - "QU" if self.unicast else "QM", |
92 | | - self.get_class_(self.class_), |
93 | | - self.name, |
94 | | - ) |
| 136 | + mode = "QU" if self.unicast else "QM" |
| 137 | + return _format_display("DNSQuestion", [("mode", mode), *self._display_fields()]) |
95 | 138 |
|
96 | 139 | def answered_by(self, rec: DNSRecord) -> bool: |
97 | 140 | return self.class_ == rec.class_ and self.type in (rec.type, _TYPE_ANY) and self.name == rec.name |
@@ -188,6 +231,17 @@ def _set_created_ttl(self, created: _float, ttl: _int) -> None: |
188 | 231 | self.created = created |
189 | 232 | self.ttl = ttl |
190 | 233 |
|
| 234 | + def _display_fields(self) -> list[tuple[str, str]]: |
| 235 | + remaining = int(self.get_remaining_ttl(current_time_millis())) |
| 236 | + return [*DNSEntry._display_fields(self), ("ttl", f"{self.ttl} ({remaining} remaining)")] |
| 237 | + |
| 238 | + def _repr_with(self, *details: tuple[str, str]) -> str: |
| 239 | + return _format_display(type(self).__name__, [*self._display_fields(), *details]) |
| 240 | + |
| 241 | + def to_string(self, other: bytes | str) -> str: |
| 242 | + """Compatibility alias rendering through the display formatter.""" |
| 243 | + return self._repr_with(("data", str(other))) |
| 244 | + |
191 | 245 | def _suppressed_by_answer(self, answer: DNSRecord) -> bool: |
192 | 246 | """True when the answer matches this record with at least half its TTL left.""" |
193 | 247 | return self == answer and self.ttl / 2 < answer.ttl |
@@ -220,14 +274,13 @@ def __hash__(self) -> int: |
220 | 274 |
|
221 | 275 | def __repr__(self) -> str: |
222 | 276 | try: |
223 | | - return self.to_string( |
224 | | - socket.inet_ntop( |
225 | | - socket.AF_INET6 if _is_v6_address(self.address) else socket.AF_INET, |
226 | | - self.address, |
227 | | - ) |
| 277 | + data = socket.inet_ntop( |
| 278 | + socket.AF_INET6 if _is_v6_address(self.address) else socket.AF_INET, |
| 279 | + self.address, |
228 | 280 | ) |
229 | 281 | except (ValueError, OSError): |
230 | | - return self.to_string(str(self.address)) |
| 282 | + data = str(self.address) |
| 283 | + return self._repr_with(("data", data)) |
231 | 284 |
|
232 | 285 | def write(self, out: DNSOutgoing) -> None: |
233 | 286 | out.write_string(self.address) |
@@ -281,6 +334,9 @@ def __hash__(self) -> int: |
281 | 334 | """Hash to compare like DNSHinfo.""" |
282 | 335 | return self._hash |
283 | 336 |
|
| 337 | + def __repr__(self) -> str: |
| 338 | + return self._repr_with(("cpu", self.cpu), ("os", self.os)) |
| 339 | + |
284 | 340 | def write(self, out: DNSOutgoing) -> None: |
285 | 341 | """Write the rdata to an outgoing packet.""" |
286 | 342 | out.write_character_string(self.cpu.encode("utf-8")) |
@@ -325,9 +381,8 @@ def __hash__(self) -> int: |
325 | 381 | return self._hash |
326 | 382 |
|
327 | 383 | def __repr__(self) -> str: |
328 | | - return self.to_string( |
329 | | - self.next_name + "," + "|".join([self.get_type(type_) for type_ in self.rdtypes]) |
330 | | - ) |
| 384 | + covered = "|".join([_type_label(t) for t in self.rdtypes]) |
| 385 | + return self._repr_with(("next_name", self.next_name), ("covers", covered)) |
331 | 386 |
|
332 | 387 | def write(self, out: DNSOutgoing) -> None: |
333 | 388 | """Write the rdata to an outgoing packet.""" |
@@ -397,7 +452,7 @@ def __hash__(self) -> int: |
397 | 452 | return self._hash |
398 | 453 |
|
399 | 454 | def __repr__(self) -> str: |
400 | | - return self.to_string(self.alias) |
| 455 | + return self._repr_with(("alias", self.alias)) |
401 | 456 |
|
402 | 457 | @property |
403 | 458 | def max_size_compressed(self) -> int: |
@@ -453,6 +508,9 @@ def __hash__(self) -> int: |
453 | 508 | """Hash to compare like DNSService.""" |
454 | 509 | return self._hash |
455 | 510 |
|
| 511 | + def __repr__(self) -> str: |
| 512 | + return self._repr_with(("server", self.server), ("port", str(self.port))) |
| 513 | + |
456 | 514 | def write(self, out: DNSOutgoing) -> None: |
457 | 515 | out.write_short(self.priority) |
458 | 516 | out.write_short(self.weight) |
@@ -514,9 +572,8 @@ def __hash__(self) -> int: |
514 | 572 | return self._hash |
515 | 573 |
|
516 | 574 | def __repr__(self) -> str: |
517 | | - if len(self.text) > 16: |
518 | | - return self.to_string(f"{len(self.text)} bytes") |
519 | | - return self.to_string(self.text) |
| 575 | + data = f"{len(self.text)} bytes" if len(self.text) > 16 else str(self.text) |
| 576 | + return self._repr_with(("data", data)) |
520 | 577 |
|
521 | 578 | def write(self, out: DNSOutgoing) -> None: |
522 | 579 | out.write_string(self.text) |
|
0 commit comments