OpenNHP/opennhp · error

inner packet too short

Error message

inner packet too short

What it means

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.

Solutions

  1. Verify the client serializes the full NHP packet header (at least 24 bytes) before sending
  2. Check the packet-construction code for premature writes or truncation (e.g. wrong buffer size)
  3. Confirm client and relay use the same NHP packet version/header layout

Example fix

// before
req, _ := http.NewRequest("POST", relayURL, bytes.NewReader(packet[:10]))
// after
if len(packet) < 24 { return errors.New("packet truncated") }
req, _ := http.NewRequest("POST", relayURL, bytes.NewReader(packet))
Defensive patterns

Strategy: validation

Validate before calling

if len(packet) < 24 {
    return fmt.Errorf("packet truncated: %d bytes, need >= 24", len(packet))
}

Try / catch

if resp.StatusCode == http.StatusBadRequest {
    b, _ := io.ReadAll(resp.Body)
    if strings.Contains(string(b), "inner packet too short") {
        return fmt.Errorf("packet builder produced only %d bytes; check header serialization", len(packet))
    }
}

Prevention

When it happens

Trigger: POSTing a body of 1-23 bytes to the relay endpoint, as exercised by TestRouting_ShortBodyReturns400.

Common situations: 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.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/1ce0b500ad82b62e. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/relay/relay.go:978

	if err != nil {
		http.Error(w, "failed to read body", http.StatusBadRequest)
		return
	}
	if len(innerPacket) == 0 {
		http.Error(w, "empty packet", http.StatusBadRequest)
		return
	}
	if len(innerPacket) > maxPacketSize {
		http.Error(w, "packet too large", http.StatusBadRequest)
		return
	}
	n := len(innerPacket)

	// Extract the counter from the inner packet header (bytes [16:24], big-endian uint64).
	// The NHP server echoes this counter in its ACK/COK response, so we use it
	// to match the response back to this HTTP request.
	if n < 24 {
		http.Error(w, "inner packet too short", http.StatusBadRequest)
		return
	}
	innerCounter := binary.BigEndian.Uint64(innerPacket[16:24])

	// Extract real client address before picking an instance so sticky
	// sessions can hash on it.
	realAddr, err := realClientAddr(r)
	if err != nil {
		log.Error("[Relay] %v", err)
		http.Error(w, "relay misconfigured: missing X-Real-IP header from local reverse proxy", http.StatusBadGateway)
		return
	}
	realAddrKey := realAddr.String()

	// Pick a target instance. When StickyInstance is enabled,
	// hash the real client IP so the same client always reaches the same
	// instance — required for stateful flows like OTP→REG where per-
	// instance local state (SQLite) must be consistent across requests.

View on GitHub (pinned to 6e04ca5ff0)