{"record":{"id":"55ea3128231af1c0","repo":"nats-io/nats-server","slug":"with-qos-v-packet-identifier-cannot-be-0","errorCode":null,"errorMessage":"with QoS=%v, packet identifier cannot be 0","messagePattern":"with QoS=(.+?), packet identifier cannot be 0","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/mqtt.go","lineNumber":4302,"sourceCode":"\t\tif changed := c.selectMappedSubject(); changed {\n\t\t\t// We need to keep track of the NATS subject/mapped in the `pp` structure.\n\t\t\tpp.subject = c.pa.subject\n\t\t\tpp.mapped = c.pa.mapped\n\t\t\t// We also now need to map the original MQTT topic to the new topic\n\t\t\t// based on the new subject.\n\t\t\tpp.topic = natsSubjectToMQTTTopic(pp.subject)\n\t\t}\n\t\t// Reset those now.\n\t\tc.pa.subject, c.pa.mapped = nil, nil\n\t}\n\n\tif qos > 0 {\n\t\tpp.pi, err = r.readUint16(\"packet identifier\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif pp.pi == 0 {\n\t\t\treturn fmt.Errorf(\"with QoS=%v, packet identifier cannot be 0\", qos)\n\t\t}\n\t} else {\n\t\tpp.pi = 0\n\t}\n\n\t// The message payload will be the total packet length minus\n\t// what we have consumed for the variable header\n\tpayloadSize := pl - (r.pos - start)\n\tif payloadSize < 0 {\n\t\treturn fmt.Errorf(\"invalid remaining length %d for PUBLISH packet\", pl)\n\t}\n\tpp.sz = payloadSize\n\tif pp.sz > 0 {\n\t\tstart = r.pos\n\t\tr.pos += pp.sz\n\t\tpp.msg = r.buf[start:r.pos]\n\t} else if pp.sz == 0 {\n\t\tpp.msg = nil","sourceCodeStart":4284,"sourceCodeEnd":4320,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/mqtt.go#L4284-L4320","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Fix the client to generate a non-zero packet identifier (e.g. an incrementing counter starting at 1) for QoS>0 PUBLISH packets","Set QoS to 0 if no acknowledgement/delivery guarantee is needed, in which case pi may be 0 or omitted","Capture the client's outbound traffic and verify the pi bytes (positions right after the topic length/topic) are non-zero","Upgrade or replace the offending client library to a spec-conformant version"],"exampleFix":"// before (client-side encoding)\npi := 0\nwriteUint16(pi)\n// after\nif qos > 0 && pi == 0 { pi = nextPacketID() }\nwriteUint16(pi)","handlingStrategy":"validation","validationCode":"func validPublish(qos byte, pi uint16) error {\n  if qos > 0 && pi == 0 { return errors.New(\"packet identifier must be non-zero for QoS>0\") }\n  return nil\n}","typeGuard":"func hasValidPacketID(qos byte, pi uint16) bool { return qos == 0 || pi != 0 }","tryCatchPattern":null,"preventionTips":["Use an incrementing packet-ID counter starting at 1, wrapping at 65535 to 1","Reserve pi=0 exclusively for QoS0 publishes","Fuzz-test your encoder against a spec-conformant broker before production","Prefer a maintained MQTT client library over hand-built packets"],"tags":["mqtt","protocol-violation","qos1","packet-identifier"],"backgroundTag":"mqtt-protocol-violation","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}