nats-io/nats-server · error

with QoS=%v, packet identifier cannot be 0

Error message

with QoS=%v, packet identifier cannot be 0

What it means

A PUBLISH packet with QoS 1 or 2 must carry a non-zero packet identifier per the MQTT spec ([MQTT-2.3.1-1]); identifier 0 is reserved for QoS 0 packets. The server rejects the packet during parsing because a zero pi would collide with QoS0 semantics and break acknowledgement tracking.

Source

Thrown at server/mqtt.go:4302

		if changed := c.selectMappedSubject(); changed {
			// We need to keep track of the NATS subject/mapped in the `pp` structure.
			pp.subject = c.pa.subject
			pp.mapped = c.pa.mapped
			// We also now need to map the original MQTT topic to the new topic
			// based on the new subject.
			pp.topic = natsSubjectToMQTTTopic(pp.subject)
		}
		// Reset those now.
		c.pa.subject, c.pa.mapped = nil, nil
	}

	if qos > 0 {
		pp.pi, err = r.readUint16("packet identifier")
		if err != nil {
			return err
		}
		if pp.pi == 0 {
			return fmt.Errorf("with QoS=%v, packet identifier cannot be 0", qos)
		}
	} else {
		pp.pi = 0
	}

	// The message payload will be the total packet length minus
	// what we have consumed for the variable header
	payloadSize := pl - (r.pos - start)
	if payloadSize < 0 {
		return fmt.Errorf("invalid remaining length %d for PUBLISH packet", pl)
	}
	pp.sz = payloadSize
	if pp.sz > 0 {
		start = r.pos
		r.pos += pp.sz
		pp.msg = r.buf[start:r.pos]
	} else if pp.sz == 0 {
		pp.msg = nil

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Fix the client to generate a non-zero packet identifier (e.g. an incrementing counter starting at 1) for QoS>0 PUBLISH packets
  2. Set QoS to 0 if no acknowledgement/delivery guarantee is needed, in which case pi may be 0 or omitted
  3. Capture the client's outbound traffic and verify the pi bytes (positions right after the topic length/topic) are non-zero
  4. Upgrade or replace the offending client library to a spec-conformant version

Example fix

// before (client-side encoding)
pi := 0
writeUint16(pi)
// after
if qos > 0 && pi == 0 { pi = nextPacketID() }
writeUint16(pi)
Defensive patterns

Strategy: validation

Validate before calling

func validPublish(qos byte, pi uint16) error {
  if qos > 0 && pi == 0 { return errors.New("packet identifier must be non-zero for QoS>0") }
  return nil
}

Type guard

func hasValidPacketID(qos byte, pi uint16) bool { return qos == 0 || pi != 0 }

Prevention

When it happens

Trigger: A client sends a PUBLISH packet whose fixed-header QoS bits are 1 or 2 but whose variable-header packet identifier field encodes 0 (two zero bytes).

Common situations: Buggy or hand-rolled MQTT clients constructing packets manually, fuzzers, corrupted frames from a faulty broker/bridge, or off-by-one encoding where the pi field is omitted or zero-filled.

Related errors


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