XTLS/Xray-core · error
empty padding schedule
Error message
empty padding schedule
What it means
validatePaddingSchedule rejected a padding schedule with zero turns. runPaddingSchedule needs at least one turn to shape the handshake; an empty slice means the schedule builder produced nothing. Since schedules are constructed in code (newClientPaddingSchedule2612), this is a programming/config-builder error, not a runtime condition.
Source
Thrown at transport/internet/finalmask/xmc/padding.go:75
}
localSends := isClient == (turn.direction == paddingClientToServer)
if localSends {
if err := writePaddingTurnWithBuffer(writer, turn, prefixLength, time.Sleep, &writeBuffer); err != nil {
return fmt.Errorf("write padding turn %d: %w", i, err)
}
continue
}
if err := readPaddingTurn(reader, turn, prefixLength); err != nil {
return fmt.Errorf("read padding turn %d: %w", i, err)
}
}
return nil
}
func validatePaddingSchedule(schedule []paddingTurn, firstTurnPrefixLength int) error {
if len(schedule) == 0 {
return fmt.Errorf("empty padding schedule")
}
if firstTurnPrefixLength < 0 {
return fmt.Errorf("negative first turn prefix length: %d", firstTurnPrefixLength)
}
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)
}View on GitHub (pinned to 7d214f8b09)
Solutions
- Ensure the schedule builder returns at least one turn for every supported path
- Fail at construction time if schedule selection yields zero turns, before any bytes are written
- Add a unit test asserting non-empty schedules for all builder inputs
Example fix
// before
schedule := buildSchedule(v) // may return nil
err := runPaddingSchedule(r, w, true, prefix, schedule)
// after
schedule := buildSchedule(v)
if len(schedule) == 0 {
return fmt.Errorf("no padding schedule for version %d", v)
}
err := runPaddingSchedule(r, w, true, prefix, schedule) Defensive patterns
Strategy: validation
Validate before calling
if len(schedule) == 0 {
return fmt.Errorf("refusing to run empty padding schedule")
} Type guard
func hasPaddingTurns(s []paddingTurn) bool { return len(s) > 0 } Prevention
- Assert non-empty schedules at construction time
- Unit-test every schedule builder output for non-emptiness
When it happens
Trigger: Calling runPaddingSchedule with a nil or empty schedule slice; a schedule builder that filters out all turns based on bad input (e.g. unsupported version profile).
Common situations: Forks adding conditional schedules that can return empty lists; tests invoking runPaddingSchedule directly with no turns; upstream changes where schedule selection fails open with an empty result.
Related errors
- negative first turn prefix length: %d
- first prefixed padding turn is not client-to-server
- padding turn %d has invalid direction: %d
- padding turn %d has an invalid start delay: %w
- padding turn %d has an invalid chunk delay: %w
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/ef3f2abe71e032a8.
Report an issue: GitHub.