{"record":{"id":"b1fbc623a1d675ee","repo":"XTLS/Xray-core","slug":"prefix-length-d-leaves-no-padding-record","errorCode":null,"errorMessage":"prefix length %d leaves no padding record","messagePattern":"prefix length (.+?) leaves no padding record","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"transport/internet/finalmask/xmc/padding.go","lineNumber":300,"sourceCode":"\tif err != nil {\n\t\treturn 0, nil, nil, err\n\t}\n\treturn targetLength, chunks, delays, nil\n}\n\nfunc trimPaddingPrefix(variant paddingVariant, prefixLength int) ([]int, []paddingDelayRange, error) {\n\tremainingPrefix := prefixLength\n\tfirstChunk := 0\n\tfor firstChunk < len(variant.chunks) && remainingPrefix > 0 {\n\t\tchunkLength := variant.chunks[firstChunk]\n\t\tif remainingPrefix < chunkLength {\n\t\t\treturn nil, nil, fmt.Errorf(\"prefix length %d splits chunk %d\", prefixLength, firstChunk)\n\t\t}\n\t\tremainingPrefix -= chunkLength\n\t\tfirstChunk++\n\t}\n\tif remainingPrefix != 0 || firstChunk == len(variant.chunks) {\n\t\treturn nil, nil, fmt.Errorf(\"prefix length %d leaves no padding record\", prefixLength)\n\t}\n\n\tchunks := append([]int(nil), variant.chunks[firstChunk:]...)\n\tdelays := make([]paddingDelayRange, len(chunks))\n\tif len(variant.delays) > 0 {\n\t\tcopy(delays, variant.delays[firstChunk:])\n\t}\n\treturn chunks, delays, nil\n}\n\nfunc defaultPaddingChunks(recordLength, writeChunkLength int) []int {\n\tchunks := make([]int, 0, (recordLength+writeChunkLength-1)/writeChunkLength)\n\tfor remaining := recordLength; remaining > 0; {\n\t\tchunkLength := min(remaining, writeChunkLength)\n\t\tchunks = append(chunks, chunkLength)\n\t\tremaining -= chunkLength\n\t}\n\treturn chunks","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/transport/internet/finalmask/xmc/padding.go#L282-L318","documentation":"After the prefix-consumption loop, either some prefix bytes remain unconsumed (remainingPrefix != 0) or every chunk of the variant was eaten (firstChunk == len(variant.chunks)), leaving zero padding records for the turn. The reader expects at least one record after the prefix, so the schedule is rejected. This is the boundary case of the prefix/chunk alignment contract enforced by trimPaddingPrefix.","triggerScenarios":"A firstTurnPrefixLength exactly equal to (or greater than) the total length of every variant in the turn, so the prefix consumes all chunks; or a prefix that is longer than the variant total leaving leftover bytes no chunk can absorb.","commonSituations":"Configuring a first turn whose variants total exactly the prefix size (e.g. prefix 4096 with a single 4096-byte chunk); mixing presets built for a smaller prefix with a peer that now sends a larger prefix after a version change.","solutions":["Add at least one extra chunk after the prefix boundary to every variant (increase total variant length beyond the prefix).","Reduce firstTurnPrefixLength so at least one chunk survives after consumption.","Use the shipped presets, which guarantee trailing records after the prefix.","Assert in tests that selectPaddingVariant(variant, prefix) returns at least one chunk for every variant in turn one."],"exampleFix":"// before: prefix 4096 consumes the only chunk, no record remains\nchunks: []int{4096}\n// after: one record survives after the prefix\nchunks: []int{4096, 512}","handlingStrategy":"validation","validationCode":"func variantSurvivesPrefix(prefix int, chunks []int) bool {\n    sum := 0\n    for _, c := range chunks {\n        sum += c\n    }\n    return prefix < sum && prefix >= chunks[0] // at least one chunk or partial remainder left\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Ensure every first-turn variant totals strictly more than the prefix length.","Test selectPaddingVariant(variant, prefix) returns len(chunks) >= 1 for all variants.","Re-run preset validation tests after changing the negotiated prefix."],"tags":["go","configuration","padding","protocol-framing"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}