{"record":{"id":"854d03d8aa3afcc7","repo":"XTLS/Xray-core","slug":"last-lengths-entry-min-can-t-be-0","errorCode":null,"errorMessage":"last lengths entry min can't be 0","messagePattern":"last lengths entry min can't be 0","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"infra/conf/transport_finalmask.go","lineNumber":276,"sourceCode":"\t\tconfig.PacketsFrom = int64(from)\n\t\tconfig.PacketsTo = int64(to)\n\t\tif config.PacketsFrom == 0 {\n\t\t\treturn nil, errors.New(\"PacketsFrom can't be 0\")\n\t\t}\n\t}\n\n\tif len(c.Lengths) > 0 {\n\t\tfor _, r := range c.Lengths {\n\t\t\tconfig.LengthsMin = append(config.LengthsMin, int64(r.From))\n\t\t\tconfig.LengthsMax = append(config.LengthsMax, int64(r.To))\n\t\t}\n\t} else {\n\t\tconfig.LengthsMin = append(config.LengthsMin, int64(c.Length.From))\n\t\tconfig.LengthsMax = append(config.LengthsMax, int64(c.Length.To))\n\t}\n\n\tif config.LengthsMin[len(config.LengthsMin)-1] == 0 {\n\t\treturn nil, errors.New(\"last lengths entry min can't be 0\")\n\t}\n\n\tif len(c.Delays) > 0 {\n\t\tfor _, r := range c.Delays {\n\t\t\tconfig.DelaysMin = append(config.DelaysMin, int64(r.From))\n\t\t\tconfig.DelaysMax = append(config.DelaysMax, int64(r.To))\n\t\t}\n\t} else {\n\t\tconfig.DelaysMin = append(config.DelaysMin, int64(c.Delay.From))\n\t\tconfig.DelaysMax = append(config.DelaysMax, int64(c.Delay.To))\n\t}\n\n\tconfig.MaxSplitMin = int64(c.MaxSplit.From)\n\tconfig.MaxSplitMax = int64(c.MaxSplit.To)\n\n\treturn config, nil\n}\n","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/infra/conf/transport_finalmask.go#L258-L294","documentation":"FragmentMask.Build (infra/conf/transport_finalmask.go:276) requires the LAST fragment-length interval to have a non-zero minimum. LengthsMin entries (from c.Lengths[] or the single c.Length) define byte-size ranges per fragment; a final entry with min 0 would allow zero-length fragments at the end of the stream, so it is rejected.","triggerScenarios":"\"lengths\":[{\"from\":100,\"to\":200},{\"from\":0,\"to\":50}] or plain \"length\":{\"from\":0,...} when lengths is absent — the last (only) entry then has min 0. Only the final entry is checked; earlier entries with from:0 are allowed (zero-size padding mid-stream).","commonSituations":"Copy-pasting interval templates where the first entry is {\"from\":0,...} and then appending more; intending 0 to mean 'any size'; generators that always emit a terminal {0,x} catch-all bucket.","solutions":["Give the last lengths entry from>=1, e.g. {\"from\":50,\"to\":100}","If using a single length range, set \"length\":{\"from\":1,...} not from:0","Move any {from:0} bucket to a non-final position"],"exampleFix":"// before\n\"lengths\": [ {\"from\":100,\"to\":200}, {\"from\":0,\"to\":64} ]\n// after\n\"lengths\": [ {\"from\":100,\"to\":200}, {\"from\":8,\"to\":64} ]","handlingStrategy":"validation","validationCode":"const lens = (cfg.lengths ?? [cfg.length]).filter(Boolean);\nif (!lens.length) throw new Error('lengths/length required');\nconst last = lens[lens.length - 1];\nif (last.from === 0) throw new Error('last lengths entry min must be >= 1');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never end a lengths list with a {from:0} bucket","Validate generator output for the terminal interval"],"tags":["xray","go","transport","fragment","validation"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}