XTLS/Xray-core · error
padding turn %d combines variants with generated write chunk
Error message
padding turn %d combines variants with generated write chunks
What it means
validatePaddingSchedule rejected turn i because it defines both explicit variants (pre-shaped chunk lists) and a non-zero writeChunkLength (randomly generated chunks). A turn must choose one shaping mode: variant-based or generator-based; combining them is ambiguous.
Source
Thrown at transport/internet/finalmask/xmc/padding.go:98
if firstTurnPrefixLength > 0 && schedule[0].direction != paddingClientToServer {
return fmt.Errorf("first prefixed padding turn is not client-to-server")
}
for i, turn := range schedule {
if turn.direction != paddingClientToServer && turn.direction != paddingServerToClient {
return fmt.Errorf("padding turn %d has invalid direction: %d", i, turn.direction)
}
if err := validatePaddingDelayRange(turn.startDelay); err != nil {
return fmt.Errorf("padding turn %d has an invalid start delay: %w", i, err)
}
if err := validatePaddingDelayRange(turn.chunkDelay); err != nil {
return fmt.Errorf("padding turn %d has an invalid chunk delay: %w", i, err)
}
if turn.writeChunkMinLength < 0 || turn.writeChunkLength < turn.writeChunkMinLength || turn.writeChunkLength > maxPaddingChunkLength {
return fmt.Errorf("padding turn %d has an invalid write chunk range: %d-%d", i, turn.writeChunkMinLength, turn.writeChunkLength)
}
if len(turn.variants) > 0 && turn.writeChunkLength != 0 {
return fmt.Errorf("padding turn %d combines variants with generated write chunks", i)
}
minLength, maxLength, err := paddingTurnBounds(turn)
if err != nil {
return fmt.Errorf("padding turn %d: %w", i, err)
}
hasSendRange := turn.sendMinLength != 0 || turn.sendMaxLength != 0
if hasSendRange {
if len(turn.variants) > 0 {
return fmt.Errorf("padding turn %d combines variants with a send range", i)
}
if turn.sendMinLength < minLength || turn.sendMaxLength < turn.sendMinLength || turn.sendMaxLength > maxLength {
return fmt.Errorf("padding turn %d has an invalid send range: %d-%d", i, turn.sendMinLength, turn.sendMaxLength)
}
}
if i == 0 && minLength-firstTurnPrefixLength < 1 {
return fmt.Errorf("padding turn 0 is too short for %d prefix bytes", firstTurnPrefixLength)
}View on GitHub (pinned to 7d214f8b09)
Solutions
- If using variants, leave writeChunkLength (and writeChunkMinLength) at 0
- If using generated chunks, remove the variants list
- Enforce mutual exclusion in the schedule builder so this cannot compile/configure its way into runtime
Example fix
// before
turn := paddingTurn{variants: vs, writeChunkLength: 8 * 1024}
// after
turn := paddingTurn{variants: vs} // generated chunks disabled Defensive patterns
Strategy: validation
Validate before calling
func variantsExclusive(turn paddingTurn) bool {
return !(len(turn.variants) > 0 && turn.writeChunkLength != 0)
} Prevention
- Enforce variants-vs-generator mutual exclusion in the schedule builder
- Clear writeChunkLength when copying variant-based turns
When it happens
Trigger: A hand-authored turn that copies a variant list and also sets writeChunkLength for tuning; merging two schedule profiles without clearing one field.
Common situations: Forks extending schedules; config formats where both fields are optional and both get filled; schedule merge/refactor mistakes.
Related errors
- first prefixed padding turn is not client-to-server
- padding turn %d has invalid direction: %d
- padding turn %d: %w
- unexpected client IP length
- empty padding schedule
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/0c9303503314e854.
Report an issue: GitHub.