I’m stuck(???) on Stage #YC9.
There is a very strange thing I found for me. Some days ago, I successfully passed test #YC9 about parsing compressed packets in DNS server challenge. Today, I started doing next stage, #GT1, about forwarding messages and noticed, that my code passes #GT1 but doesn’t pass #YC9. After some hours of debugging, I found that problem is in parsing compressed question. Also, I found that my program breaks at pointer octet, and I logged it. It looks like this: 11000000
, so the pointer is 0 and programs goes to the begging of the buffer, thinking that it is a label sequence.
I rolled back my code to the last commit that passes previous stage (#YC9), and the programs had broken again! In my logs below you can see that octet at 47 index is 11000000
, and if you calculate octets, you will see that exactly this octet must be a pointer (12 to header + 1 + 3 + 1 + 17 + 1 + 3 + 1 + 2 + 2 +1 + 3 = 47, so 46th octet is the f
, last octet of the def
label). It goes after label, it is not ASCII symbol (so there is no doubts about this octet is not a part of label) and it begins with two 1.
In newer code (not as below) I found myself believing that I don’t modify the buffer I parsing by marking every buffer pointer by const
, so the buffer is exactly how it was read.
So my question is: other people (and me, lol) passed this test. Can someone check him test? Am I doing something wrong?
Here are my logs (previous tests have been passed):
[33m[tester::#YC9] [94mRunning tests for Stage #YC9 (Parse compressed packet)
[33m[tester::#YC9] [94mStarting DNS server on 127.0.0.1:2053
[33m[tester::#YC9] [94mConnecting to 127.0.0.1:2053 using UDP
[33m[your_program] Logs from your program will appear here!
[33m[your_program]
[33m[your_program] Request question labels (1):
[33m[your_program] codecrafters.io.
[33m[your_program] Written 64 bytes
[33m[tester::#YC9] [94mQuerying the following in the same request (Messages with >> prefix are part of this log)
[33m[tester::#YC9] [94m>> ;abc.longassdomainname.com. IN A
[33m[tester::#YC9] [94m>> ;def.longassdomainname.com. IN A
[33m[tester::#YC9] [91mDNS query failed: dns: bad rdata.
[33m[tester::#YC9] [91mIf you are seeing this after a while then it is likely that your server is not responding with appropriate id
[33m[tester::#YC9] [91mTest failed (try setting 'debug: true' in your codecrafters.yml to see more details)
[33m[your_program]
[33m[your_program] BYTE: 47
[33m[your_program] BITSET: 11000000
[33m[your_program] PTR: 0
[33m[your_program] Request question labels (2):
[33m[your_program] abc.longassdomainname.com.
[33m[your_program] def.�
BYTE and so on was written when my code faced pointer byte. Below, you can see my parsing code:
inline bool byte_get_bit(std::byte byte, char bit) {
std::byte mask = std::byte{1} << bit;
return (byte & mask) != std::byte{0};
}
DNS::dns_message DNS::dns_message::parse_from_buffer(void *buffer) {
dns_message result {};
std::byte *byte_buf = reinterpret_cast<std::byte*>(buffer);
result.packet_id = ntohs(reinterpret_cast<std::uint16_t*>(byte_buf)[0]);
result.flag_qr = byte_get_bit(byte_buf[2], 7);
result.opcode = std::to_integer<std::uint8_t>((byte_buf[2] & std::byte{0b0111'1000}) >> 3);
result.flag_aa = byte_get_bit(byte_buf[2], 2);
result.flag_tc = byte_get_bit(byte_buf[2], 1);
result.flag_rd = byte_get_bit(byte_buf[2], 0);
result.flag_ra = byte_get_bit(byte_buf[3], 7);
result.reserved = std::to_integer<std::uint8_t>((byte_buf[3] & std::byte{0b0111'0000}) >> 4);
result.rcode = std::to_integer<std::uint8_t>(byte_buf[3] & std::byte{0b0000'1111});
std::uint16_t qdcount = ntohs(reinterpret_cast<std::uint16_t*>(byte_buf+4)[0]);
std::uint16_t ancount = ntohs(reinterpret_cast<std::uint16_t*>(byte_buf+6)[0]); // to parse answers
result.nscount_ = ntohs(reinterpret_cast<std::uint16_t*>(byte_buf+8)[0]);
result.arcount_ = ntohs(reinterpret_cast<std::uint16_t*>(byte_buf+10)[0]);
std::byte *read_byte = byte_buf + 12;
for (int i = 0; i < qdcount; ++i) {
std::byte *old_read_byte_compressed;
bool read_compressed = false;
dns_question q;
bool first_write = true;
while (*read_byte != std::byte{0}) { // WARNING: memory issue if invalid packet format
if ((*read_byte & std::byte{0xC0}) == std::byte{0xC0}) {
std::uint8_t ptr = std::to_integer<std::uint8_t>(*read_byte & std::byte{0x3F});
if (first_write) {
std::cout << "BYTE: " << read_byte - byte_buf << std::endl;
std::cout << "BITSET: " << std::bitset<8>((std::uint8_t)*read_byte) << std::endl;
std::cout << "PTR: " << (int)ptr << std::endl;
first_write = false;
}
old_read_byte_compressed = read_byte;
read_byte = byte_buf + ptr;
read_compressed = true;
}
int label_size = std::to_integer<int>(*read_byte++);
std::string label(label_size, '\0');
for (char &ch : label) ch = std::to_integer<int>(*read_byte++);
q.labels.push_back(label);
}
if (read_compressed) read_byte = old_read_byte_compressed;
++read_byte; // skip zero octet or pointer octet
q.qtype = ntohs(reinterpret_cast<std::uint16_t*>(read_byte)[0]);
read_byte += 2;
q.qclass = ntohs(reinterpret_cast<std::uint16_t*>(read_byte)[0]);
read_byte += 2;
result.questions.push_back(q);
}
return result;
}
It is used simply like this:
// (In while)
// Receive data
bytesRead = recvfrom(udpSocket, buffer, sizeof(buffer), 0, reinterpret_cast<struct sockaddr*>(&clientAddress), &clientAddrLen);
if (bytesRead == -1) {
perror("Error receiving data");
break;
}
DNS::dns_message request = DNS::dns_message::parse_from_buffer(buffer);
I feel that there must be something simple, but… I can’t figure it out.