nats-io/nats-server · error
invalid remaining length %d for packet type %x
Error message
invalid remaining length %d for packet type %x
What it means
For certain MQTT control packets (PINGREQ, PINGRESP, DISCONNECT, etc.), the spec mandates a remaining length of exactly 0 bytes. mqttCheckRemainingLength enforces this after the varint remaining-length is decoded and returns this error when pl != expected. It guards against clients sending payloads on packets that must be empty.
Source
Thrown at server/mqtt.go:1030
return fmt.Errorf("invalid fixed header flags %x for packet type %x", flags, packetType)
}
return nil
}
func mqttCheckRemainingLength(packetType byte, pl int) error {
var expected int
switch packetType {
case mqttPacketConnect, mqttPacketPub, mqttPacketSub, mqttPacketUnsub:
return nil
case mqttPacketPubAck, mqttPacketPubRec, mqttPacketPubRel, mqttPacketPubComp:
expected = 2
case mqttPacketPing, mqttPacketDisconnect:
expected = 0
default:
return nil
}
if pl != expected {
return fmt.Errorf("invalid remaining length %d for packet type %x", pl, packetType)
}
return nil
}
func (c *client) mqttTraceMsg(msg []byte) {
maxTrace := c.srv.getOpts().MaxTracedMsgLen
if maxTrace > 0 && len(msg) > maxTrace {
c.Tracef("<<- MSG_PAYLOAD: [\"%s...\"]", msg[:maxTrace])
} else {
c.Tracef("<<- MSG_PAYLOAD: [%q]", msg)
}
}
// The MQTT client connection has been closed, or the DISCONNECT packet was received.
// For a "clean" session, we will delete the session, otherwise, simply removing
// the binding. We will also send the "will" message if applicable.
//
// Runs from the client's readLoop.View on GitHub (pinned to 3a66a489d2)
Solutions
- Fix the client encoder so PINGREQ/DISCONNECT have zero-length remaining length.
- Enable MQTT trace to inspect the raw packet and remaining-length bytes.
- Check for buggy middleware that appends padding to frames.
- Reconnect with a conformant client; the server correctly rejects the malformed packet per spec.
Example fix
// before sendPacket(DISCONNECT, payload=extraBytes) // after sendPacket(DISCONNECT, remainingLength=0)
Defensive patterns
Strategy: validation
Validate before calling
// Enforce zero remaining length for PINGREQ/DISCONNECT etc.
const ZERO_LEN_TYPES = new Set([12, 13, 14]); // PINGREQ, PINGRESP, DISCONNECT
if (ZERO_LEN_TYPES.has(type) && remainingLength !== 0) throw new Error('payload not allowed on type ' + type); Type guard
function requiresZeroRemainingLength(packetType) { return [12, 13, 14].includes(packetType); } Prevention
- Send DISCONNECT/PINGREQ with empty bodies
- Don't append trailing/padding bytes to control packets
- Compute remaining length from an empty payload for fixed-length types
- Fuzz-test your encoder against a spec-conformant broker
When it happens
Trigger: A client sends PINGREQ or DISCONNECT (or another fixed-length type) with a nonzero remaining-length varint. Triggered in mqttCheckRemainingLength when pl != expected.
Common situations: Client libraries appending trailing bytes or session state to DISCONNECT; corruption inserting bytes; hand-rolled encoders computing length incorrectly; fuzz tests.
Related errors
- invalid fixed header flags %x for packet type %x
- topic filter cannot be empty
- malformed variable int
- the first packet should be a CONNECT (%v), got %v
- received unknown packet type %d
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/4b0391feac9121a4.
Report an issue: GitHub.