{"record":{"id":"334d8bb2f2537c79","repo":"weaviate/weaviate","slug":"invalid-packed-digests-length-d-not-a-multiple-o","errorCode":null,"errorMessage":"invalid packed digests length %d: not a multiple of %d","messagePattern":"invalid packed digests length (.+?): not a multiple of (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"usecases/replica/repair_digests_codec.go","lineNumber":52,"sourceCode":"\tout := make([]byte, 0, len(digests)*CompareDigestsRecordLength)\n\tvar buf [CompareDigestsRecordLength]byte\n\tfor _, d := range digests {\n\t\tcopy(buf[:16], d.ID[:])\n\t\tbinary.BigEndian.PutUint64(buf[16:24], uint64(d.UpdateTime))\n\t\tbuf[24] = 0\n\t\tif d.Deleted {\n\t\t\tbuf[24] = CompareDigestsFlagDeleted\n\t\t}\n\t\tout = append(out, buf[:]...)\n\t}\n\treturn out\n}\n\n// RepairDigestsFromBinary decodes a RepairDigestsToBinary payload, rejecting\n// any length that is not a whole number of records.\nfunc RepairDigestsFromBinary(data []byte) ([]types.RepairDigest, error) {\n\tif len(data)%CompareDigestsRecordLength != 0 {\n\t\treturn nil, fmt.Errorf(\"invalid packed digests length %d: not a multiple of %d\",\n\t\t\tlen(data), CompareDigestsRecordLength)\n\t}\n\tdigests := make([]types.RepairDigest, len(data)/CompareDigestsRecordLength)\n\tfor i := range digests {\n\t\trec := data[i*CompareDigestsRecordLength : (i+1)*CompareDigestsRecordLength]\n\t\tid, err := uuid.FromBytes(rec[:16])\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"parse uuid from binary record: %w\", err)\n\t\t}\n\t\tdigests[i] = types.RepairDigest{\n\t\t\tID:         id,\n\t\t\tUpdateTime: int64(binary.BigEndian.Uint64(rec[16:24])),\n\t\t\tDeleted:    rec[24]&CompareDigestsFlagDeleted != 0,\n\t\t}\n\t}\n\treturn digests, nil\n}\n","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/weaviate/weaviate/blob/75aa4b6d11f8818305aafd4440b4e32794f7ca04/usecases/replica/repair_digests_codec.go#L34-L70","documentation":"RepairDigestsFromBinary decodes a packed payload of fixed-size records (CompareDigestsRecordLength = 25 bytes: 16-byte UUID + 8-byte big-endian timestamp + 1 flag byte). If the total length is not an exact multiple of the record size, the payload is malformed — it cannot be split into whole records — and decoding is rejected before any parsing.","triggerScenarios":"RepairDigestsFromBinary or decodePackedDigests receiving a payload whose byte length modulo 25 is nonzero: a peer sending the packed encoding with trailing bytes, truncated gRPC/REST transfer, an older peer sending the repeated-proto payload into the packed decoder, or compression/framing bytes left in the buffer.","commonSituations":"Version skew where one node claims RepairDigestsEncodingPacked but sends the legacy repeated-proto body; network truncation; a proxy or middleware altering the body; handcrafted test payloads.","solutions":["Verify the sender negotiated the packed encoding (RepairDigestsEncodingPacked) and did not fall back to the proto encoding — mismatched encodings change the byte layout.","Log the payload length and compare against (expected record count) * 25 to spot truncation or trailing bytes.","Retransmit the digest payload; a single corrupted transfer is the usual cause.","If a peer persistently produces bad lengths, check its Weaviate version for codec bugs and align versions."],"exampleFix":"// before: assume packed blindly | digests, err := replica.RepairDigestsFromBinary(body) | // after: guard on negotiated encoding first | if encoding == replica.RepairDigestsEncodingPacked { digests, err = replica.RepairDigestsFromBinary(body) } else { digests = decodeRepeatedProto(body) }","handlingStrategy":"validation","validationCode":"const recordLen = 25; if len(payload)%recordLen != 0 { return fmt.Errorf(\"payload %d not multiple of %d — wrong encoding or truncated\", len(payload), recordLen) }","typeGuard":null,"tryCatchPattern":"digests, err := replica.RepairDigestsFromBinary(body); if err != nil { /* fall back to the repeated-proto decoding path or request retransmission */ return nil, fmt.Errorf(\"packed digests rejected: %w\", err) }","preventionTips":["Always send the encoding identifier alongside the payload so the decoder matches the writer's format.","Check payload length before sending: len must equal recordCount * 25.","Guard against proxies or middleware that re-frame or compress gRPC/REST bodies.","Keep peer Weaviate versions aligned; legacy peers must stay on the repeated-proto path (encoding 0)."],"tags":["go","wire-format","grpc","codec","length-validation"],"backgroundTag":"invalid-payload-length","analyzedSha":"75aa4b6d11f8818305aafd4440b4e32794f7ca04","analyzedAt":"2026-09-04T14:58:20.392Z","contentChangedAt":"2026-09-04T14:58:20.392Z","schemaVersion":2},"datasetVersion":"2026-09-11T21:17:09.523Z"}