{"record":{"id":"1ce0b500ad82b62e","repo":"OpenNHP/opennhp","slug":"inner-packet-too-short","errorCode":null,"errorMessage":"inner packet too short","messagePattern":"inner packet too short","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"endpoints/relay/relay.go","lineNumber":978,"sourceCode":"\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\n\t// sessions can hash on it.\n\trealAddr, err := realClientAddr(r)\n\tif err != nil {\n\t\tlog.Error(\"[Relay] %v\", err)\n\t\thttp.Error(w, \"relay misconfigured: missing X-Real-IP header from local reverse proxy\", http.StatusBadGateway)\n\t\treturn\n\t}\n\trealAddrKey := realAddr.String()\n\n\t// Pick a target instance. When StickyInstance is enabled,\n\t// hash the real client IP so the same client always reaches the same\n\t// instance — required for stateful flows like OTP→REG where per-\n\t// instance local state (SQLite) must be consistent across requests.","sourceCodeStart":960,"sourceCodeEnd":996,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/relay/relay.go#L960-L996","documentation":"handleRelay responds with HTTP 400 'inner packet too short' when the body is non-empty and within the size cap but shorter than 24 bytes, which is the minimum needed to read the big-endian uint64 counter at bytes [16:24]. The counter is required to match the NHP server's ACK/COK response back to this HTTP request, so short payloads cannot be processed.","triggerScenarios":"POSTing a body of 1-23 bytes to the relay endpoint, as exercised by TestRouting_ShortBodyReturns400.","commonSituations":"Truncated packet from a buggy serializer; client sending only a partial header; garbage/garbled test payloads; packet built for a different protocol version with a smaller header layout.","solutions":["Verify the client serializes the full NHP packet header (at least 24 bytes) before sending","Check the packet-construction code for premature writes or truncation (e.g. wrong buffer size)","Confirm client and relay use the same NHP packet version/header layout"],"exampleFix":"// before\nreq, _ := http.NewRequest(\"POST\", relayURL, bytes.NewReader(packet[:10]))\n// after\nif len(packet) < 24 { return errors.New(\"packet truncated\") }\nreq, _ := http.NewRequest(\"POST\", relayURL, bytes.NewReader(packet))","handlingStrategy":"validation","validationCode":"if len(packet) < 24 {\n    return fmt.Errorf(\"packet truncated: %d bytes, need >= 24\", len(packet))\n}","typeGuard":null,"tryCatchPattern":"if resp.StatusCode == http.StatusBadRequest {\n    b, _ := io.ReadAll(resp.Body)\n    if strings.Contains(string(b), \"inner packet too short\") {\n        return fmt.Errorf(\"packet builder produced only %d bytes; check header serialization\", len(packet))\n    }\n}","preventionTips":["Validate the 24-byte minimum header client-side before POSTing","Pin client and relay to the same NHP packet version","Test the packet builder output length in unit tests"],"tags":["http","validation","packet-format"],"backgroundTag":"schema-validation-failed","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"}