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

  1. Fix the client encoder so PINGREQ/DISCONNECT have zero-length remaining length.
  2. Enable MQTT trace to inspect the raw packet and remaining-length bytes.
  3. Check for buggy middleware that appends padding to frames.
  4. 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

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


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/4b0391feac9121a4. Report an issue: GitHub.