@axpecter/lync 2.2.1 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -143
- package/package.json +1 -1
- package/src/Types.luau +22 -16
- package/src/api/Group.luau +40 -33
- package/src/api/Packet.luau +68 -54
- package/src/api/Query.luau +103 -65
- package/src/api/Scope.luau +26 -10
- package/src/api/Signal.luau +43 -52
- package/src/codec/Base.luau +21 -14
- package/src/codec/composite/Array.luau +56 -134
- package/src/codec/composite/Map.luau +103 -72
- package/src/codec/composite/Optional.luau +6 -2
- package/src/codec/composite/Shared.luau +160 -69
- package/src/codec/composite/Struct.luau +283 -42
- package/src/codec/composite/Tagged.luau +13 -16
- package/src/codec/composite/Tuple.luau +21 -15
- package/src/codec/datatype/Buffer.luau +6 -4
- package/src/codec/datatype/CFrame.luau +56 -44
- package/src/codec/datatype/Color.luau +10 -10
- package/src/codec/datatype/Instance.luau +15 -12
- package/src/codec/datatype/IntVector.luau +2 -2
- package/src/codec/datatype/NumberRange.luau +15 -8
- package/src/codec/datatype/Ray.luau +13 -14
- package/src/codec/datatype/Rect.luau +12 -12
- package/src/codec/datatype/Region.luau +13 -14
- package/src/codec/datatype/Sequence.luau +87 -65
- package/src/codec/datatype/String.luau +17 -10
- package/src/codec/datatype/UDim.luau +10 -8
- package/src/codec/datatype/Vector.luau +20 -59
- package/src/codec/meta/Auto.luau +94 -126
- package/src/codec/meta/Bitfield.luau +12 -14
- package/src/codec/meta/Custom.luau +3 -1
- package/src/codec/meta/Enum.luau +9 -9
- package/src/codec/meta/Float.luau +6 -28
- package/src/codec/meta/Nothing.luau +1 -1
- package/src/codec/meta/Unknown.luau +10 -7
- package/src/codec/primitive/Bool.luau +6 -4
- package/src/codec/primitive/Float16.luau +5 -2
- package/src/codec/primitive/Int.luau +5 -6
- package/src/codec/primitive/Number.luau +6 -4
- package/src/codec/primitive/Signed.luau +2 -2
- package/src/codec/primitive/Varint.luau +69 -38
- package/src/index.d.ts +161 -53
- package/src/init.luau +109 -103
- package/src/internal/Baseline.luau +9 -1
- package/src/internal/Channel.luau +153 -154
- package/src/internal/Middleware.luau +22 -6
- package/src/internal/Pool.luau +12 -4
- package/src/internal/Registry.luau +25 -16
- package/src/internal/Transport.luau +1 -1
- package/src/transport/Bridge.luau +47 -33
- package/src/transport/Client.luau +25 -21
- package/src/transport/Gate.luau +227 -172
- package/src/transport/Reader.luau +162 -105
- package/src/transport/Server.luau +46 -57
- package/src/util/Array.luau +18 -0
- package/src/util/Buffer.luau +92 -0
- package/src/util/Constants.luau +30 -0
- package/src/util/Log.luau +68 -0
- package/src/util/Player.luau +14 -0
- package/src/util/Quantize.luau +60 -0
- package/src/internal/Util.luau +0 -26
package/src/transport/Gate.luau
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
--!native
|
|
3
3
|
-- Schema-aware validation and rate limiting.
|
|
4
4
|
|
|
5
|
+
local Log = require(script.Parent.Parent.util.Log)
|
|
5
6
|
local Types = require(script.Parent.Parent.Types)
|
|
6
7
|
|
|
7
8
|
-- Constants --------------------------------------------------------------
|
|
@@ -9,6 +10,9 @@ local Types = require(script.Parent.Parent.Types)
|
|
|
9
10
|
local DEFAULT_DEPTH = 16
|
|
10
11
|
local STRIKE_DECAY_SEC = 5
|
|
11
12
|
|
|
13
|
+
local VEC2_AXES = table.freeze({ "X", "Y" })
|
|
14
|
+
local VEC3_AXES = table.freeze({ "X", "Y", "Z" })
|
|
15
|
+
|
|
12
16
|
-- State ------------------------------------------------------------------
|
|
13
17
|
|
|
14
18
|
local _depth = DEFAULT_DEPTH
|
|
@@ -27,17 +31,20 @@ local floor = math.floor
|
|
|
27
31
|
local clock = os.clock
|
|
28
32
|
local minN = math.min
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
-- Validators return string?: nil = accepted, non-nil = rejection reason.
|
|
35
|
+
local validateWithCodec -- forward decl (recursive)
|
|
36
|
+
|
|
37
|
+
local function checkScalarBounds(value: number, minV: number?, maxV: number?): string?
|
|
31
38
|
if not isfinite(value) then
|
|
32
|
-
return
|
|
39
|
+
return "non-finite"
|
|
33
40
|
end
|
|
34
41
|
if minV and value < minV then
|
|
35
|
-
return
|
|
42
|
+
return `below min {minV}`
|
|
36
43
|
end
|
|
37
44
|
if maxV and value > maxV then
|
|
38
|
-
return
|
|
45
|
+
return `above max {maxV}`
|
|
39
46
|
end
|
|
40
|
-
return
|
|
47
|
+
return nil
|
|
41
48
|
end
|
|
42
49
|
|
|
43
50
|
local function checkVectorComponents(
|
|
@@ -45,22 +52,24 @@ local function checkVectorComponents(
|
|
|
45
52
|
components: { string },
|
|
46
53
|
minV: number,
|
|
47
54
|
maxV: number
|
|
48
|
-
):
|
|
55
|
+
): string?
|
|
49
56
|
for _, axis in components do
|
|
50
57
|
local v = value[axis]
|
|
51
58
|
if not isfinite(v) then
|
|
52
|
-
return
|
|
59
|
+
return `component {axis} non-finite`
|
|
53
60
|
end
|
|
54
61
|
if v < minV or v > maxV then
|
|
55
|
-
return
|
|
62
|
+
return `component {axis} ({v}) out of [{minV}, {maxV}]`
|
|
56
63
|
end
|
|
57
64
|
end
|
|
58
|
-
return
|
|
65
|
+
return nil
|
|
59
66
|
end
|
|
60
67
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
--[[
|
|
69
|
+
Untyped fallback NaN/inf scan. Used when a codec lacks specific metadata
|
|
70
|
+
(e.g. user custom codec) and as the last step of validateWithCodec.
|
|
71
|
+
Bounded by `depth` so a self-referential table can't recurse forever.
|
|
72
|
+
]]
|
|
64
73
|
local function scanValue(value: any, depth: number): boolean
|
|
65
74
|
if depth <= 0 then
|
|
66
75
|
return true
|
|
@@ -125,168 +134,228 @@ local function scanValue(value: any, depth: number): boolean
|
|
|
125
134
|
return true
|
|
126
135
|
end
|
|
127
136
|
|
|
128
|
-
local function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
local function validateString(value: any, codec: Types.InternalCodec<any>): string?
|
|
138
|
+
local maxLen = codec._maxStringLength
|
|
139
|
+
if maxLen and #(value :: string) > maxLen then
|
|
140
|
+
return `string length {#value} exceeds max {maxLen}`
|
|
141
|
+
end
|
|
142
|
+
return nil
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
local function validateEnum(value: any, codec: Types.InternalCodec<any>): string?
|
|
146
|
+
local valid = codec._enumValues
|
|
147
|
+
if valid and valid[value] == nil then
|
|
148
|
+
return `not a valid enum value: "{value}"`
|
|
149
|
+
end
|
|
150
|
+
return nil
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
local function validateInteger(value: any, codec: Types.InternalCodec<any>): string?
|
|
154
|
+
if not isfinite(value) then
|
|
155
|
+
return "non-finite"
|
|
156
|
+
end
|
|
157
|
+
if floor(value) ~= value then
|
|
158
|
+
return "non-integer"
|
|
159
|
+
end
|
|
160
|
+
return checkScalarBounds(value, codec._min, codec._max)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
local function validateTagged(value: any, codec: Types.InternalCodec<any>, depth: number): string?
|
|
164
|
+
if typeof(value) ~= "table" then
|
|
165
|
+
return "expected table for tagged variant"
|
|
166
|
+
end
|
|
167
|
+
local tagField = codec._tagField :: string
|
|
168
|
+
local tagName = value[tagField]
|
|
169
|
+
if tagName == nil then
|
|
170
|
+
return `missing tagField "{tagField}"`
|
|
171
|
+
end
|
|
172
|
+
local variants = codec._variants :: { [string]: Types.InternalCodec<any> }
|
|
173
|
+
local variant = variants[tagName]
|
|
174
|
+
if not variant then
|
|
175
|
+
return `unknown variant "{tagName}"`
|
|
135
176
|
end
|
|
177
|
+
return validateWithCodec(value, variant, depth - 1)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
local function validateArray(value: any, codec: Types.InternalCodec<any>, depth: number): string?
|
|
181
|
+
if typeof(value) ~= "table" then
|
|
182
|
+
return "expected array"
|
|
183
|
+
end
|
|
184
|
+
local maxCount = codec._maxCount
|
|
185
|
+
local len = #value
|
|
186
|
+
if maxCount and len > maxCount then
|
|
187
|
+
return `array length {len} exceeds max {maxCount}`
|
|
188
|
+
end
|
|
189
|
+
local element = codec._element :: Types.InternalCodec<any>
|
|
190
|
+
local nextDepth = depth - 1
|
|
191
|
+
for i = 1, len do
|
|
192
|
+
local reason = validateWithCodec(value[i], element, nextDepth)
|
|
193
|
+
if reason then
|
|
194
|
+
return `[{i}]: {reason}`
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
return nil
|
|
198
|
+
end
|
|
136
199
|
|
|
137
|
-
|
|
138
|
-
if
|
|
200
|
+
local function validateMap(value: any, codec: Types.InternalCodec<any>, depth: number): string?
|
|
201
|
+
if typeof(value) ~= "table" then
|
|
202
|
+
return "expected map"
|
|
203
|
+
end
|
|
204
|
+
local maxCount = codec._maxCount
|
|
205
|
+
local keyCodec = codec._keyCodec :: Types.InternalCodec<any>
|
|
206
|
+
local valCodec = codec._valueCodec :: Types.InternalCodec<any>
|
|
207
|
+
local nextDepth = depth - 1
|
|
208
|
+
local count = 0
|
|
209
|
+
for k, v in value do
|
|
210
|
+
count += 1
|
|
211
|
+
if maxCount and count > maxCount then
|
|
212
|
+
return `map size exceeds max {maxCount}`
|
|
213
|
+
end
|
|
214
|
+
local reason = validateWithCodec(k, keyCodec, nextDepth)
|
|
215
|
+
if reason then
|
|
216
|
+
return `key: {reason}`
|
|
217
|
+
end
|
|
218
|
+
reason = validateWithCodec(v, valCodec, nextDepth)
|
|
219
|
+
if reason then
|
|
220
|
+
return `value at key "{tostring(k)}": {reason}`
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
return nil
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
local function validateTuple(value: any, codec: Types.InternalCodec<any>, depth: number): string?
|
|
227
|
+
if typeof(value) ~= "table" then
|
|
228
|
+
return "expected tuple"
|
|
229
|
+
end
|
|
230
|
+
local elements = codec._elements :: { Types.InternalCodec<any> }
|
|
231
|
+
local nextDepth = depth - 1
|
|
232
|
+
for i = 1, #elements do
|
|
233
|
+
local reason = validateWithCodec(value[i], elements[i], nextDepth)
|
|
234
|
+
if reason then
|
|
235
|
+
return `[{i}]: {reason}`
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
return nil
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
local function validateStruct(value: any, codec: Types.InternalCodec<any>, depth: number): string?
|
|
242
|
+
if typeof(value) ~= "table" then
|
|
243
|
+
return "expected table for struct"
|
|
244
|
+
end
|
|
245
|
+
local nextDepth = depth - 1
|
|
246
|
+
for key, fieldCodec in codec._schema :: { [string]: Types.InternalCodec<any> } do
|
|
247
|
+
local fieldVal = value[key]
|
|
248
|
+
-- Bool fields default to false when missing; everything else must be present.
|
|
249
|
+
if fieldVal == nil and not fieldCodec._isBool then
|
|
250
|
+
return `missing field "{key}"`
|
|
251
|
+
end
|
|
252
|
+
local reason = validateWithCodec(fieldVal, fieldCodec, nextDepth)
|
|
253
|
+
if reason then
|
|
254
|
+
return `field "{key}": {reason}`
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
return nil
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
function validateWithCodec(value: any, codec: Types.InternalCodec<any>, depth: number): string?
|
|
261
|
+
-- Peel Optional layers in a loop so optional(optional(X)) chains stay flat.
|
|
262
|
+
while codec._isOptional do
|
|
263
|
+
if depth <= 0 then
|
|
264
|
+
return nil
|
|
265
|
+
end
|
|
139
266
|
if value == nil then
|
|
140
|
-
return
|
|
267
|
+
return nil
|
|
141
268
|
end
|
|
142
|
-
|
|
143
|
-
|
|
269
|
+
codec = codec._inner :: Types.InternalCodec<any>
|
|
270
|
+
depth -= 1
|
|
271
|
+
end
|
|
272
|
+
if depth <= 0 then
|
|
273
|
+
return nil
|
|
144
274
|
end
|
|
145
275
|
|
|
146
|
-
-- typeCheck (cheap fail-fast for non-composite codecs).
|
|
147
276
|
local typeCheck = codec._typeCheck
|
|
148
277
|
if typeCheck and typeof(value) ~= typeCheck then
|
|
149
|
-
return
|
|
278
|
+
return `expected {typeCheck}, got {typeof(value)}`
|
|
150
279
|
end
|
|
151
280
|
|
|
152
|
-
if
|
|
153
|
-
|
|
281
|
+
if typeCheck == "string" then
|
|
282
|
+
local reason = validateString(value, codec)
|
|
283
|
+
if reason then
|
|
284
|
+
return reason
|
|
285
|
+
end
|
|
154
286
|
end
|
|
155
287
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
288
|
+
if codec._isBool then
|
|
289
|
+
return nil
|
|
290
|
+
end
|
|
159
291
|
if codec._isEnum then
|
|
160
|
-
|
|
161
|
-
if valid and valid[value] == nil then
|
|
162
|
-
return false, `not a valid enum value: "{value}"`
|
|
163
|
-
end
|
|
164
|
-
return true, nil
|
|
292
|
+
return validateEnum(value, codec)
|
|
165
293
|
end
|
|
166
|
-
|
|
167
294
|
if codec._isInteger then
|
|
168
|
-
|
|
169
|
-
return false, "non-finite"
|
|
170
|
-
end
|
|
171
|
-
if floor(value) ~= value then
|
|
172
|
-
return false, "non-integer"
|
|
173
|
-
end
|
|
174
|
-
return checkScalarBounds(value, codec._min, codec._max)
|
|
295
|
+
return validateInteger(value, codec)
|
|
175
296
|
end
|
|
176
297
|
|
|
177
|
-
-- Vector quantized: per-component range check.
|
|
178
298
|
if (typeCheck == "Vector2" or typeCheck == "Vector3") and codec._min and codec._max then
|
|
179
299
|
local axes = if typeCheck == "Vector2" then VEC2_AXES else VEC3_AXES
|
|
180
300
|
return checkVectorComponents(value, axes, codec._min, codec._max)
|
|
181
301
|
end
|
|
182
|
-
|
|
183
|
-
-- Scalar quantized float (number with explicit range).
|
|
184
302
|
if typeCheck == "number" and codec._min ~= nil and codec._max ~= nil then
|
|
185
303
|
return checkScalarBounds(value, codec._min, codec._max)
|
|
186
304
|
end
|
|
187
305
|
|
|
188
306
|
if codec._isTagged then
|
|
189
|
-
|
|
190
|
-
return false, "expected table for tagged variant"
|
|
191
|
-
end
|
|
192
|
-
local tagField = codec._tagField :: string
|
|
193
|
-
local tagName = value[tagField]
|
|
194
|
-
if tagName == nil then
|
|
195
|
-
return false, `missing tagField "{tagField}"`
|
|
196
|
-
end
|
|
197
|
-
local variants = codec._variants :: { [string]: Types.InternalCodec<any> }
|
|
198
|
-
local variant = variants[tagName]
|
|
199
|
-
if not variant then
|
|
200
|
-
return false, `unknown variant "{tagName}"`
|
|
201
|
-
end
|
|
202
|
-
return validateWithCodec(value, variant, depth - 1)
|
|
307
|
+
return validateTagged(value, codec, depth)
|
|
203
308
|
end
|
|
204
|
-
|
|
205
309
|
if codec._isArray then
|
|
206
|
-
|
|
207
|
-
return false, "expected array"
|
|
208
|
-
end
|
|
209
|
-
local maxCount = codec._maxCount
|
|
210
|
-
local len = #value
|
|
211
|
-
if maxCount and len > maxCount then
|
|
212
|
-
return false, `array length {len} exceeds max {maxCount}`
|
|
213
|
-
end
|
|
214
|
-
local element = codec._element :: Types.InternalCodec<any>
|
|
215
|
-
local nextDepth = depth - 1
|
|
216
|
-
for i = 1, len do
|
|
217
|
-
local ok, reason = validateWithCodec(value[i], element, nextDepth)
|
|
218
|
-
if not ok then
|
|
219
|
-
return false, `[{i}]: {reason}`
|
|
220
|
-
end
|
|
221
|
-
end
|
|
222
|
-
return true, nil
|
|
310
|
+
return validateArray(value, codec, depth)
|
|
223
311
|
end
|
|
224
|
-
|
|
225
312
|
if codec._isMap then
|
|
226
|
-
|
|
227
|
-
return false, "expected map"
|
|
228
|
-
end
|
|
229
|
-
local maxCount = codec._maxCount
|
|
230
|
-
local keyCodec = codec._keyCodec :: Types.InternalCodec<any>
|
|
231
|
-
local valCodec = codec._valueCodec :: Types.InternalCodec<any>
|
|
232
|
-
local nextDepth = depth - 1
|
|
233
|
-
local count = 0
|
|
234
|
-
for k, v in value do
|
|
235
|
-
count += 1
|
|
236
|
-
if maxCount and count > maxCount then
|
|
237
|
-
return false, `map size exceeds max {maxCount}`
|
|
238
|
-
end
|
|
239
|
-
local ok, reason = validateWithCodec(k, keyCodec, nextDepth)
|
|
240
|
-
if not ok then
|
|
241
|
-
return false, `key: {reason}`
|
|
242
|
-
end
|
|
243
|
-
ok, reason = validateWithCodec(v, valCodec, nextDepth)
|
|
244
|
-
if not ok then
|
|
245
|
-
return false, `value at key "{tostring(k)}": {reason}`
|
|
246
|
-
end
|
|
247
|
-
end
|
|
248
|
-
return true, nil
|
|
313
|
+
return validateMap(value, codec, depth)
|
|
249
314
|
end
|
|
250
|
-
|
|
251
315
|
if codec._isTuple then
|
|
252
|
-
|
|
253
|
-
return false, "expected tuple"
|
|
254
|
-
end
|
|
255
|
-
local elements = codec._elements :: { Types.InternalCodec<any> }
|
|
256
|
-
local nextDepth = depth - 1
|
|
257
|
-
for i = 1, #elements do
|
|
258
|
-
local ok, reason = validateWithCodec(value[i], elements[i], nextDepth)
|
|
259
|
-
if not ok then
|
|
260
|
-
return false, `[{i}]: {reason}`
|
|
261
|
-
end
|
|
262
|
-
end
|
|
263
|
-
return true, nil
|
|
316
|
+
return validateTuple(value, codec, depth)
|
|
264
317
|
end
|
|
265
|
-
|
|
266
318
|
if codec._schema then
|
|
267
|
-
|
|
268
|
-
return false, "expected table for struct"
|
|
269
|
-
end
|
|
270
|
-
local nextDepth = depth - 1
|
|
271
|
-
for key, fieldCodec in codec._schema do
|
|
272
|
-
local fieldVal = value[key]
|
|
273
|
-
if fieldVal == nil and not fieldCodec._isBool then
|
|
274
|
-
return false, `missing field "{key}"`
|
|
275
|
-
end
|
|
276
|
-
local ok, reason = validateWithCodec(fieldVal, fieldCodec, nextDepth)
|
|
277
|
-
if not ok then
|
|
278
|
-
return false, `field "{key}": {reason}`
|
|
279
|
-
end
|
|
280
|
-
end
|
|
281
|
-
return true, nil
|
|
319
|
+
return validateStruct(value, codec, depth)
|
|
282
320
|
end
|
|
283
321
|
|
|
284
|
-
--
|
|
285
|
-
-- (raw f32/f64, untagged Roblox types, buffer, etc.).
|
|
322
|
+
-- No specific metadata: fall back to a depth-bounded NaN/inf scan.
|
|
286
323
|
if not scanValue(value, depth) then
|
|
287
|
-
return
|
|
324
|
+
return "NaN/inf detected"
|
|
325
|
+
end
|
|
326
|
+
return nil
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
local function getOrCreateBucket(
|
|
330
|
+
map: { [Player]: Types.RateLimitState },
|
|
331
|
+
player: Player,
|
|
332
|
+
burst: number,
|
|
333
|
+
now: number
|
|
334
|
+
): Types.RateLimitState
|
|
335
|
+
local state = map[player]
|
|
336
|
+
if not state then
|
|
337
|
+
state = { tokens = burst, lastRefill = now, lastAccepted = 0 }
|
|
338
|
+
map[player] = state
|
|
288
339
|
end
|
|
289
|
-
return
|
|
340
|
+
return state
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
local function consumeToken(
|
|
344
|
+
state: Types.RateLimitState,
|
|
345
|
+
now: number,
|
|
346
|
+
capacity: number,
|
|
347
|
+
rate: number
|
|
348
|
+
): boolean
|
|
349
|
+
local elapsed = now - state.lastRefill
|
|
350
|
+
state.lastRefill = now
|
|
351
|
+
state.tokens = minN(capacity, state.tokens + elapsed * rate)
|
|
352
|
+
|
|
353
|
+
if state.tokens >= 1 then
|
|
354
|
+
state.tokens -= 1
|
|
355
|
+
state.lastAccepted = now
|
|
356
|
+
return true
|
|
357
|
+
end
|
|
358
|
+
return false
|
|
290
359
|
end
|
|
291
360
|
|
|
292
361
|
-- Public -----------------------------------------------------------------
|
|
@@ -295,12 +364,12 @@ local Gate = {}
|
|
|
295
364
|
|
|
296
365
|
function Gate.setDepth(depth: number): ()
|
|
297
366
|
if depth < 1 then
|
|
298
|
-
error(`
|
|
367
|
+
Log.error(`depth must be >= 1, got {depth}`)
|
|
299
368
|
end
|
|
300
369
|
_depth = depth
|
|
301
370
|
end
|
|
302
371
|
|
|
303
|
-
function Gate.validate(value: any, codec: Types.InternalCodec<any>):
|
|
372
|
+
function Gate.validate(value: any, codec: Types.InternalCodec<any>): string?
|
|
304
373
|
return validateWithCodec(value, codec, _depth)
|
|
305
374
|
end
|
|
306
375
|
|
|
@@ -322,12 +391,10 @@ function Gate.checkRateLimit(reg: Types.Registration, player: Player): boolean
|
|
|
322
391
|
_rateLimits[reg.id] = perPacket
|
|
323
392
|
end
|
|
324
393
|
|
|
325
|
-
local
|
|
326
|
-
|
|
327
|
-
state = { tokens = config.burst or 1, lastRefill = now, lastAccepted = 0 }
|
|
328
|
-
perPacket[player] = state
|
|
329
|
-
end
|
|
394
|
+
local burst = config.burst or 1
|
|
395
|
+
local state = getOrCreateBucket(perPacket, player, burst, now)
|
|
330
396
|
|
|
397
|
+
-- Cooldown form: reject anything within `cooldown` seconds of the last accept.
|
|
331
398
|
if config.cooldown then
|
|
332
399
|
if now - state.lastAccepted < config.cooldown then
|
|
333
400
|
return false
|
|
@@ -340,17 +407,7 @@ function Gate.checkRateLimit(reg: Types.Registration, player: Player): boolean
|
|
|
340
407
|
if not maxPerSecond then
|
|
341
408
|
return true
|
|
342
409
|
end
|
|
343
|
-
|
|
344
|
-
local elapsed = now - state.lastRefill
|
|
345
|
-
state.lastRefill = now
|
|
346
|
-
state.tokens = minN(config.burst or 1, state.tokens + elapsed * maxPerSecond)
|
|
347
|
-
|
|
348
|
-
if state.tokens >= 1 then
|
|
349
|
-
state.tokens -= 1
|
|
350
|
-
state.lastAccepted = now
|
|
351
|
-
return true
|
|
352
|
-
end
|
|
353
|
-
return false
|
|
410
|
+
return consumeToken(state, now, burst, maxPerSecond)
|
|
354
411
|
end
|
|
355
412
|
|
|
356
413
|
function Gate.checkGlobalRateLimit(player: Player): boolean
|
|
@@ -360,27 +417,14 @@ function Gate.checkGlobalRateLimit(player: Player): boolean
|
|
|
360
417
|
end
|
|
361
418
|
|
|
362
419
|
local now = clock()
|
|
363
|
-
local state = _globalLimits
|
|
364
|
-
|
|
365
|
-
state = { tokens = maxPS, lastRefill = now, lastAccepted = 0 }
|
|
366
|
-
_globalLimits[player] = state
|
|
367
|
-
end
|
|
368
|
-
|
|
369
|
-
local elapsed = now - state.lastRefill
|
|
370
|
-
state.lastRefill = now
|
|
371
|
-
state.tokens = minN(maxPS, state.tokens + elapsed * maxPS)
|
|
372
|
-
|
|
373
|
-
if state.tokens >= 1 then
|
|
374
|
-
state.tokens -= 1
|
|
375
|
-
return true
|
|
376
|
-
end
|
|
377
|
-
return false
|
|
420
|
+
local state = getOrCreateBucket(_globalLimits, player, maxPS, now)
|
|
421
|
+
return consumeToken(state, now, maxPS, maxPS)
|
|
378
422
|
end
|
|
379
423
|
|
|
380
424
|
--[[
|
|
381
|
-
Bandwidth check
|
|
382
|
-
|
|
383
|
-
|
|
425
|
+
Bandwidth check. Frames at or below softLimit refund a strike; frames
|
|
426
|
+
above add one. Strikes decay with idle time so a quiet player isn't
|
|
427
|
+
penalized indefinitely after a noisy burst.
|
|
384
428
|
]]
|
|
385
429
|
function Gate.checkBandwidth(player: Player, bytes: number): boolean
|
|
386
430
|
if not _bandwidthSoftLimit then
|
|
@@ -430,4 +474,15 @@ function Gate.clearPlayer(player: Player): ()
|
|
|
430
474
|
end
|
|
431
475
|
end
|
|
432
476
|
|
|
477
|
+
function Gate.reset(): ()
|
|
478
|
+
_depth = DEFAULT_DEPTH
|
|
479
|
+
_globalMaxPerSecond = nil
|
|
480
|
+
_bandwidthSoftLimit = nil
|
|
481
|
+
_bandwidthMaxStrikes = 10
|
|
482
|
+
table.clear(_rateLimits)
|
|
483
|
+
table.clear(_globalLimits)
|
|
484
|
+
table.clear(_bandwidthStrikes)
|
|
485
|
+
table.clear(_bandwidthLastSeen)
|
|
486
|
+
end
|
|
487
|
+
|
|
433
488
|
return table.freeze(Gate)
|