{"record":{"id":"7d9218fbadfb9bf2","repo":"OpenNHP/opennhp","slug":"empty-packet","errorCode":null,"errorMessage":"empty packet","messagePattern":"empty packet","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"endpoints/relay/relay.go","lineNumber":965,"sourceCode":"\t}\n\n\tcr, status, errMsg := rs.resolveServer(r)\n\tif cr == nil {\n\t\thttp.Error(w, errMsg, status)\n\t\treturn\n\t}\n\n\t// Read inner NHP packet from request body. Cap at maxPacketSize+1 so we\n\t// can reject oversize bodies without pulling an unbounded amount into\n\t// memory. A single r.Body.Read() is not guaranteed to return the full\n\t// payload; io.ReadAll drains until EOF.\n\tinnerPacket, err := io.ReadAll(io.LimitReader(r.Body, int64(maxPacketSize)+1))\n\tif err != nil {\n\t\thttp.Error(w, \"failed to read body\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tif len(innerPacket) == 0 {\n\t\thttp.Error(w, \"empty packet\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tif len(innerPacket) > maxPacketSize {\n\t\thttp.Error(w, \"packet too large\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tn := len(innerPacket)\n\n\t// Extract the counter from the inner packet header (bytes [16:24], big-endian uint64).\n\t// The NHP server echoes this counter in its ACK/COK response, so we use it\n\t// to match the response back to this HTTP request.\n\tif n < 24 {\n\t\thttp.Error(w, \"inner packet too short\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tinnerCounter := binary.BigEndian.Uint64(innerPacket[16:24])\n\n\t// Extract real client address before picking an instance so sticky","sourceCodeStart":947,"sourceCodeEnd":983,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/relay/relay.go#L947-L983","documentation":"handleRelay responds with HTTP 400 'empty packet' when the request body contains zero bytes after reading. An empty payload cannot be a valid inner NHP packet, so the relay rejects it before parsing the header counter or forwarding to a server instance.","triggerScenarios":"POSTing to the relay endpoint with an empty body (Content-Length: 0 or no body at all), as exercised by TestRouting_EmptyBodyReturns400.","commonSituations":"Client code builds the HTTP request but forgets to attach the serialized NHP packet; a serializer returns nil/empty bytes on error and the error is swallowed; curl/Postman tests without a payload.","solutions":["Attach the serialized inner NHP packet as the request body before sending","Check client-side serialization for silent failures returning empty bytes and handle those errors before POSTing","Ensure Content-Length or chunked encoding actually carries data"],"exampleFix":"// before\nreq, _ := http.NewRequest(\"POST\", relayURL, nil)\n// after\nif len(packet) == 0 { return errors.New(\"no packet to send\") }\nreq, _ := http.NewRequest(\"POST\", relayURL, bytes.NewReader(packet))","handlingStrategy":"validation","validationCode":"if len(packet) == 0 {\n    return errors.New(\"refusing to send empty NHP packet\")\n}","typeGuard":null,"tryCatchPattern":"if resp.StatusCode == http.StatusBadRequest {\n    b, _ := io.ReadAll(resp.Body)\n    if strings.Contains(string(b), \"empty packet\") {\n        return fmt.Errorf(\"client sent empty body: packet builder returned %d bytes\", len(packet))\n    }\n}","preventionTips":["Assert packet non-empty before every POST","Handle serializer errors instead of swallowing them (empty bytes often mean a failed build)","Add a client unit test that the built packet has length > 0"],"tags":["http","validation","relay"],"backgroundTag":"empty-required-field","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}