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

  1. 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.
  2. If you control both ends, use uniform chunk sizes that divide the prefix length evenly.
  3. Revert to the built-in presets (newClientPaddingSchedule2612/newServerPaddingSchedule2612) which are validated against the negotiated prefix.
  4. 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

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


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/a5bc363d2ab4f8af. Report an issue: GitHub.