refactor: use bit operations for the DNS over TCP length prefix - #3466
Open
pjfanning wants to merge 1 commit into
Open
refactor: use bit operations for the DNS over TCP length prefix#3466pjfanning wants to merge 1 commit into
pjfanning wants to merge 1 commit into
Conversation
RFC 1035 section 4.2.2 prefixes each DNS over TCP message with its length as two big endian bytes. Both directions expressed this with arithmetic rather than bit operations: encodeLength: ByteString((length / 256).toByte, length.toByte) decodeLength: ((data(0).toInt + 256) % 256) * 256 + ((data(1) + 256) % 256) The `+ 256) % 256` pairs are working around Byte being signed, which `& 0xFF` says directly, and the division and multiplication are a shift. This is behaviour preserving, not a fix. `decodeLength` was verified identical over all 65536 byte pairs, and `encodeLength` over the whole valid length range 0 to 65535. The two forms diverge only for negative lengths, which cannot occur: the only caller passes a ByteString length. Adds tests for the prefix itself: a round trip over every representable length, the expected big endian encoding, decoding of bytes with the high bit set, and that trailing bytes after the prefix are ignored. These pass against both the old and the new implementation, as a behaviour preserving change requires.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
RFC 1035 section 4.2.2 prefixes each DNS over TCP message with its length as two big endian bytes. Both directions in
TcpDnsClientexpressed this with arithmetic rather than bit operations:The
+ 256) % 256pairs are working aroundBytebeing signed, which& 0xFFstates directly, and the division and multiplication are a shift by 8.Modification
Plus a short comment naming the RFC section, since the two byte big endian framing is not obvious from the code alone.
This is behaviour preserving, not a fix
I want to be clear that this fixes nothing — the original was correct. I verified equivalence exhaustively rather than by inspection:
decodeLength: identical over all 65,536 byte pairs (every(Byte, Byte)combination), producing the full[0, 65535]range in both forms.encodeLength: identical over the entire valid length range 0 to 65535.The two forms diverge in exactly one place:
encodeLength(-1)gives(0, -1)under the old form and(-1, -1)under the new one. That cannot occur — the only caller isencodeLength(bytes.length)atTcpDnsClient.scala:62, wherebytesis aByteString, so the argument is never negative. I checked the call site rather than assuming.So the case for merging is readability, and a small amount of work removed from the receive path (two divisions and a multiplication become a shift and two masks). If you would rather not take churn in working DNS framing code for that, this is a reasonable one to decline.
Tests
Added a
"The TCP DNS length prefix"block toTcpDnsClientSpeccovering the helpers directly, which nothing previously did — they were only exercised indirectly through the client's receive loop:0xFF 0xFF→ 65535,0x80 0x80→ 32896) — the case the signed-byte workaround existed forThese pass against both the old and the new implementation. That is the correct outcome for a behaviour preserving change, and worth stating plainly: unlike a bug fix, these tests do not fail on the unmodified code. Their value is locking the contract down so a future change to this framing cannot silently break it.
actor-tests/testOnly org.apache.pekko.io.dns.internal.TcpDnsClientSpec— 8 tests, all pass.sbt actor/mimaReportBinaryIssuesclean;TcpDnsClientisprivate[internal], so no API surface is affected.scalafmtrun on both modules.References
Found during a sweep for patterns left over from the Java 8 / Scala 2.12 baseline, alongside #3465.