A resolver sends query bytes and gets response bytes back. Today you make that exchange an injectable function so the whole resolver is testable without a network - and use it to check the reply's ID echoes the query's.
Send a query through an injectable transport and verify the response ID matches.
A real resolver would open a UDP socket, send the query bytes, and read the reply.
That socket is OS-specific and impossible to test with exact values, so we hide it
behind a seam: an injectable function query([]byte) -> ([]byte, error) that
takes request bytes and returns response bytes. In production it wraps a socket; in
a test it returns scripted bytes you control. The resolver never knows the
difference, which is what keeps every remaining lesson exact and offline.
The first thing the resolver does with a reply is check the ID. You chose the ID when building the query (lesson 11), and the server must echo it back so you can match a response to the question you asked - important because a UDP socket might receive a stray or forged packet. A mismatched ID means “this is not my answer,” and the resolver rejects it. This seam plus the ID check is the backbone the rest of the chapter hangs on.
type Transport func(request []byte) (response []byte, err error)type Resolver struct{ send Transport }// today ask only parses the header; lesson 25 returns a full Messagefunc (r *Resolver) ask(id uint16, name string) (Header, error) {resp, err := r.send(BuildQuery(id, name))// parse the header; reject if its ID != id}