XTLS/Xray-core · error
prefix length %d splits chunk %d
Error message
prefix length %d splits chunk %d
What it means
trimPaddingPrefix consumes the first turn's prefix by walking whole padding chunks of a variant; this error fires when the prefix length falls strictly inside a chunk instead of ending exactly on a chunk boundary. The prefix and the chunked padding records share one stream, so a misaligned prefix would desynchronize framing. It surfaces during schedule validation (validatePaddingSchedule -> selectPaddingVariant -> trimPaddingPrefix) and aborts connection setup.
Source
Thrown at transport/internet/finalmask/xmc/padding.go:294
if variantIndex < 0 || variantIndex >= len(turn.variants) {
return 0, nil, nil, fmt.Errorf("invalid send variant index: %d", variantIndex)
}
variant := turn.variants[variantIndex]
targetLength := paddingVariantLength(variant)
chunks, delays, err := trimPaddingPrefix(variant, prefixLength)
if err != nil {
return 0, nil, nil, err
}
return targetLength, chunks, delays, nil
}
func trimPaddingPrefix(variant paddingVariant, prefixLength int) ([]int, []paddingDelayRange, error) {
remainingPrefix := prefixLength
firstChunk := 0
for firstChunk < len(variant.chunks) && remainingPrefix > 0 {
chunkLength := variant.chunks[firstChunk]
if remainingPrefix < chunkLength {
return nil, nil, fmt.Errorf("prefix length %d splits chunk %d", prefixLength, firstChunk)
}
remainingPrefix -= chunkLength
firstChunk++
}
if remainingPrefix != 0 || firstChunk == len(variant.chunks) {
return nil, nil, fmt.Errorf("prefix length %d leaves no padding record", prefixLength)
}
chunks := append([]int(nil), variant.chunks[firstChunk:]...)
delays := make([]paddingDelayRange, len(chunks))
if len(variant.delays) > 0 {
copy(delays, variant.delays[firstChunk:])
}
return chunks, delays, nil
}
func defaultPaddingChunks(recordLength, writeChunkLength int) []int {
chunks := make([]int, 0, (recordLength+writeChunkLength-1)/writeChunkLength)View on GitHub (pinned to 7d214f8b09)
Solutions
- Align the prefix with chunk boundaries: make the leading chunks of every variant sum exactly to the firstTurnPrefixLength (e.g. one chunk equal to the whole prefix), or lower the prefix below the first chunk so it is fully consumed by chunk 0.
- If you control both ends, use uniform chunk sizes that divide the prefix length evenly.
- Revert to the built-in presets (newClientPaddingSchedule2612/newServerPaddingSchedule2612) which are validated against the negotiated prefix.
- Write a unit test that calls validatePaddingSchedule(schedule, prefix) for your custom schedule to catch this before deploy.
Example fix
// before: prefix 1000 splits the single 1500-byte chunk
variants: []paddingVariant{{chunks: []int{1500, 2000}}}
// after: leading chunk exactly covers the prefix
variants: []paddingVariant{{chunks: []int{1000, 500, 2000}}} Defensive patterns
Strategy: validation
Validate before calling
func prefixAlignedWithChunks(prefix int, chunks []int) bool {
rem := prefix
for _, c := range chunks {
if rem < c {
return false
}
rem -= c
}
return rem == 0
}
// before building the schedule:
for _, v := range turn1.variants {
if !prefixAlignedWithChunks(firstTurnPrefixLength, v.chunks) {
return errors.New("prefix splits a variant chunk")
}
} Prevention
- Run validatePaddingSchedule(schedule, firstTurnPrefixLength) in a unit test for every custom preset.
- Keep leading chunks sized to exactly cover the negotiated prefix.
- Never mix presets from different protocol versions.
When it happens
Trigger: Setting a firstTurnPrefixLength (from the protocol handshake, e.g. the initial record/packet prefix) whose value is not an exact sum of leading chunk lengths of a chosen padding variant. Happens when custom padding_preset variants use chunk sizes that do not divide the negotiated prefix, or when a copied preset is paired with a different prefix length.
Common situations: Customizing the 1.26.12-style padding presets (padding_preset.go) by changing chunk sizes while keeping the original first-turn prefix; upgrading the transport so the server sends a different prefix length than the client preset expects; hand-writing a variants-based schedule without accounting for the prefix record.
Related errors
- prefix length %d leaves no padding record
- invalid range: %d-%d
- variants cannot be combined with a length range
- variant %d has no chunks
- metrics must have a tag or listen address
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/a5bc363d2ab4f8af.
Report an issue: GitHub.