XTLS/Xray-core · error
variants cannot be combined with a length range
Error message
variants cannot be combined with a length range
What it means
A paddingTurn can describe its lengths either as an explicit minLength/maxLength range or as a list of concrete variants, but not both. This error fires in paddingTurnBounds when variants are present and either bound field is non-zero, because the two sources of length bounds would conflict.
Source
Thrown at transport/internet/finalmask/xmc/padding.go:329
func defaultPaddingChunks(recordLength, writeChunkLength int) []int {
chunks := make([]int, 0, (recordLength+writeChunkLength-1)/writeChunkLength)
for remaining := recordLength; remaining > 0; {
chunkLength := min(remaining, writeChunkLength)
chunks = append(chunks, chunkLength)
remaining -= chunkLength
}
return chunks
}
func paddingTurnBounds(turn paddingTurn) (int, int, error) {
if len(turn.variants) == 0 {
if turn.minLength < 1 || turn.maxLength < turn.minLength || turn.maxLength > maxPaddingTurnLength {
return 0, 0, fmt.Errorf("invalid range: %d-%d", turn.minLength, turn.maxLength)
}
return turn.minLength, turn.maxLength, nil
}
if turn.minLength != 0 || turn.maxLength != 0 {
return 0, 0, fmt.Errorf("variants cannot be combined with a length range")
}
minLength := maxPaddingTurnLength + 1
maxLength := 0
for i, variant := range turn.variants {
if len(variant.chunks) == 0 {
return 0, 0, fmt.Errorf("variant %d has no chunks", i)
}
if len(variant.delays) != 0 && len(variant.delays) != len(variant.chunks) {
return 0, 0, fmt.Errorf("variant %d has %d chunks and %d delays", i, len(variant.chunks), len(variant.delays))
}
for j, chunkLength := range variant.chunks {
if chunkLength < 1 || chunkLength > maxPaddingChunkLength {
return 0, 0, fmt.Errorf("variant %d chunk %d has invalid length: %d", i, j, chunkLength)
}
if len(variant.delays) > 0 {
if err := validatePaddingDelayRange(variant.delays[j]); err != nil {
return 0, 0, fmt.Errorf("variant %d chunk %d has an invalid delay: %w", i, j, err)View on GitHub (pinned to 7d214f8b09)
Solutions
- Pick one representation: remove minLength/maxLength (leave both 0) when using variants.
- Or drop the variants slice and express the turn as a plain length range.
- Sanitize config loading to zero out bounds when variants are detected, if configs come from users.
Example fix
// before
turn := paddingTurn{minLength: 64, maxLength: 64, variants: vs}
// after
turn := paddingTurn{variants: vs} Defensive patterns
Strategy: validation
Validate before calling
if len(turn.variants) > 0 && (turn.minLength != 0 || turn.maxLength != 0) {
return errors.New("turn declares both variants and a length range")
} Prevention
- Zero out min/max fields whenever a variants list is set.
- In config loaders, reject JSON objects containing both keys.
- Add a linter/test over generated schedules asserting mutual exclusivity.
When it happens
Trigger: Constructing a paddingTurn with a non-empty variants slice while minLength or maxLength is also set (non-zero); e.g. appending a range to a variant-based preset turn, or leaving a stale MaxLength in shared config JSON.
Common situations: Merging a variants-based preset with a config template that still carries min/max padding fields; YAML/JSON config where empty range defaults serialize as 0 but an explicit value survives an edit.
Related errors
- invalid range: %d-%d
- variant %d has no chunks
- empty padding schedule
- negative first turn prefix length: %d
- first prefixed padding turn is not client-to-server
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/8bf97f5d401450e6.
Report an issue: GitHub.