@axpecter/lync 1.4.3 → 1.5.0
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 +41 -2
- package/package.json +1 -1
- package/src/Types.luau +13 -1
- package/src/api/Group.luau +35 -28
- package/src/api/Namespace.luau +107 -89
- package/src/api/Packet.luau +69 -51
- package/src/api/Query.luau +101 -95
- package/src/api/Scope.luau +32 -18
- package/src/api/Signal.luau +78 -56
- package/src/codec/Base.luau +17 -17
- package/src/codec/composite/Array.luau +74 -74
- package/src/codec/composite/Map.luau +80 -80
- package/src/codec/composite/Optional.luau +14 -14
- package/src/codec/composite/Shared.luau +35 -35
- package/src/codec/composite/Struct.luau +108 -108
- package/src/codec/composite/Tagged.luau +71 -71
- package/src/codec/composite/Tuple.luau +35 -35
- package/src/codec/datatype/Buffer.luau +18 -18
- package/src/codec/datatype/CFrame.luau +24 -24
- package/src/codec/datatype/Color.luau +8 -12
- package/src/codec/datatype/Instance.luau +13 -13
- package/src/codec/datatype/IntVector.luau +17 -17
- package/src/codec/datatype/NumberRange.luau +7 -7
- package/src/codec/datatype/Ray.luau +16 -16
- package/src/codec/datatype/Rect.luau +13 -13
- package/src/codec/datatype/Region.luau +32 -36
- package/src/codec/datatype/Sequence.luau +41 -41
- package/src/codec/datatype/String.luau +28 -28
- package/src/codec/datatype/UDim.luau +17 -17
- package/src/codec/datatype/Vector.luau +14 -18
- package/src/codec/meta/Auto.luau +75 -73
- package/src/codec/meta/Bitfield.luau +46 -46
- package/src/codec/meta/Custom.luau +7 -7
- package/src/codec/meta/Enum.luau +25 -25
- package/src/codec/meta/Nothing.luau +3 -3
- package/src/codec/meta/Quantized.luau +63 -60
- package/src/codec/meta/Unknown.luau +11 -11
- package/src/codec/primitive/Bool.luau +12 -12
- package/src/codec/primitive/Float16.luau +28 -28
- package/src/codec/primitive/Number.luau +41 -41
- package/src/codec/primitive/Varint.luau +19 -19
- package/src/init.luau +81 -61
- package/src/internal/Baseline.luau +7 -7
- package/src/internal/Channel.luau +56 -56
- package/src/internal/Middleware.luau +29 -29
- package/src/internal/Pool.luau +15 -15
- package/src/internal/Registry.luau +19 -19
- package/src/transport/Bridge.luau +17 -17
- package/src/transport/Client.luau +46 -46
- package/src/transport/Gate.luau +56 -64
- package/src/transport/Reader.luau +34 -34
- package/src/transport/Server.luau +89 -89
package/src/transport/Gate.luau
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--!strict
|
|
2
|
-
--!
|
|
2
|
+
--!native
|
|
3
3
|
-- Post-deserialization security: NaN/inf, rate limiting, custom validate.
|
|
4
4
|
|
|
5
|
-
local Types = require
|
|
5
|
+
local Types = require(script.Parent.Parent.Types)
|
|
6
6
|
|
|
7
7
|
type Registration = Types.Registration
|
|
8
8
|
type RateLimitConfig = Types.RateLimitConfig
|
|
9
9
|
|
|
10
|
-
-- Constants
|
|
10
|
+
-- Constants -----------------------------------------------------------
|
|
11
11
|
|
|
12
12
|
local DEFAULT_MAX_DEPTH = 16
|
|
13
13
|
|
|
@@ -15,7 +15,7 @@ local isfinite = math.isfinite
|
|
|
15
15
|
local min = math.min
|
|
16
16
|
local clock = os.clock
|
|
17
17
|
|
|
18
|
-
-- State
|
|
18
|
+
-- State ---------------------------------------------------------------
|
|
19
19
|
|
|
20
20
|
type Bucket = {
|
|
21
21
|
tokens: number,
|
|
@@ -27,24 +27,23 @@ local _buckets = {} :: { [Player]: { [number]: Bucket } }
|
|
|
27
27
|
local _dropHandlers =
|
|
28
28
|
{} :: { (player: Player, reason: string, packetName: string, data: any?) -> () }
|
|
29
29
|
|
|
30
|
-
-- Private
|
|
30
|
+
-- Private -------------------------------------------------------------
|
|
31
31
|
|
|
32
|
-
local function fireDrop
|
|
32
|
+
local function fireDrop(player: Player, reason: string, packetName: string, data: any?): ()
|
|
33
33
|
for _, handler in _dropHandlers do
|
|
34
|
-
handler
|
|
34
|
+
handler(player, reason, packetName, data)
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
local function isClean (value: any, depth: number): boolean
|
|
38
|
+
local function isClean(value: any, depth: number): boolean
|
|
40
39
|
if depth > _maxDepth then
|
|
41
40
|
return false
|
|
42
41
|
end
|
|
43
42
|
|
|
44
|
-
local t = type
|
|
43
|
+
local t = type(value)
|
|
45
44
|
|
|
46
45
|
if t == "number" then
|
|
47
|
-
return isfinite
|
|
46
|
+
return isfinite(value)
|
|
48
47
|
end
|
|
49
48
|
|
|
50
49
|
-- String, boolean, nil are always clean. Exit before table/vector/userdata checks.
|
|
@@ -55,7 +54,7 @@ local function isClean (value: any, depth: number): boolean
|
|
|
55
54
|
if t == "table" then
|
|
56
55
|
local deeper = depth + 1
|
|
57
56
|
for k, v in value do
|
|
58
|
-
if not isClean
|
|
57
|
+
if not isClean(k, deeper) or not isClean(v, deeper) then
|
|
59
58
|
return false
|
|
60
59
|
end
|
|
61
60
|
end
|
|
@@ -63,60 +62,53 @@ local function isClean (value: any, depth: number): boolean
|
|
|
63
62
|
end
|
|
64
63
|
|
|
65
64
|
if t == "vector" then
|
|
66
|
-
return isfinite
|
|
65
|
+
return isfinite(value.X) and isfinite(value.Y) and isfinite(value.Z)
|
|
67
66
|
end
|
|
68
67
|
|
|
69
68
|
if t == "userdata" then
|
|
70
|
-
local ut = typeof
|
|
69
|
+
local ut = typeof(value)
|
|
71
70
|
|
|
72
71
|
if ut == "Vector2" then
|
|
73
|
-
return isfinite
|
|
72
|
+
return isfinite(value.X) and isfinite(value.Y)
|
|
74
73
|
elseif ut == "Color3" then
|
|
75
|
-
return isfinite
|
|
74
|
+
return isfinite(value.R) and isfinite(value.G) and isfinite(value.B)
|
|
76
75
|
elseif ut == "CFrame" then
|
|
77
76
|
local position = value.Position
|
|
78
|
-
if
|
|
79
|
-
not isfinite (position.X)
|
|
80
|
-
or not isfinite (position.Y)
|
|
81
|
-
or not isfinite (position.Z)
|
|
82
|
-
then
|
|
77
|
+
if not isfinite(position.X) or not isfinite(position.Y) or not isfinite(position.Z) then
|
|
83
78
|
return false
|
|
84
79
|
end
|
|
85
|
-
local axis, angle = value:ToAxisAngle
|
|
86
|
-
return isfinite
|
|
87
|
-
and isfinite (axis.X)
|
|
88
|
-
and isfinite (axis.Y)
|
|
89
|
-
and isfinite (axis.Z)
|
|
80
|
+
local axis, angle = value:ToAxisAngle()
|
|
81
|
+
return isfinite(angle) and isfinite(axis.X) and isfinite(axis.Y) and isfinite(axis.Z)
|
|
90
82
|
elseif ut == "UDim" then
|
|
91
|
-
return isfinite
|
|
83
|
+
return isfinite(value.Scale)
|
|
92
84
|
elseif ut == "UDim2" then
|
|
93
|
-
return isfinite
|
|
85
|
+
return isfinite(value.X.Scale) and isfinite(value.Y.Scale)
|
|
94
86
|
elseif ut == "NumberRange" then
|
|
95
|
-
return isfinite
|
|
87
|
+
return isfinite(value.Min) and isfinite(value.Max)
|
|
96
88
|
elseif ut == "Rect" then
|
|
97
|
-
return isfinite
|
|
98
|
-
and isfinite
|
|
99
|
-
and isfinite
|
|
100
|
-
and isfinite
|
|
89
|
+
return isfinite(value.Min.X)
|
|
90
|
+
and isfinite(value.Min.Y)
|
|
91
|
+
and isfinite(value.Max.X)
|
|
92
|
+
and isfinite(value.Max.Y)
|
|
101
93
|
elseif ut == "Ray" then
|
|
102
94
|
local origin = value.Origin
|
|
103
95
|
local direction = value.Direction
|
|
104
|
-
return isfinite
|
|
105
|
-
and isfinite
|
|
106
|
-
and isfinite
|
|
107
|
-
and isfinite
|
|
108
|
-
and isfinite
|
|
109
|
-
and isfinite
|
|
96
|
+
return isfinite(origin.X)
|
|
97
|
+
and isfinite(origin.Y)
|
|
98
|
+
and isfinite(origin.Z)
|
|
99
|
+
and isfinite(direction.X)
|
|
100
|
+
and isfinite(direction.Y)
|
|
101
|
+
and isfinite(direction.Z)
|
|
110
102
|
elseif ut == "Region3" then
|
|
111
103
|
local position = value.CFrame.Position
|
|
112
|
-
return isfinite
|
|
104
|
+
return isfinite(position.X) and isfinite(position.Y) and isfinite(position.Z)
|
|
113
105
|
end
|
|
114
106
|
end
|
|
115
107
|
|
|
116
108
|
return true
|
|
117
109
|
end
|
|
118
110
|
|
|
119
|
-
local function checkRate
|
|
111
|
+
local function checkRate(player: Player, reg: Registration): boolean
|
|
120
112
|
local cfg = reg.rateLimit
|
|
121
113
|
if not cfg then
|
|
122
114
|
return true
|
|
@@ -128,7 +120,7 @@ local function checkRate (player: Player, reg: Registration): boolean
|
|
|
128
120
|
end
|
|
129
121
|
|
|
130
122
|
local id = reg.id
|
|
131
|
-
local now = clock
|
|
123
|
+
local now = clock()
|
|
132
124
|
local cap = cfg.burstAllowance or cfg.maxPerSecond
|
|
133
125
|
local bucket = playerBuckets[id]
|
|
134
126
|
|
|
@@ -141,7 +133,7 @@ local function checkRate (player: Player, reg: Registration): boolean
|
|
|
141
133
|
end
|
|
142
134
|
|
|
143
135
|
local elapsed = now - bucket.lastRef
|
|
144
|
-
bucket.tokens = min
|
|
136
|
+
bucket.tokens = min(cap, bucket.tokens + elapsed * cfg.maxPerSecond)
|
|
145
137
|
bucket.lastRef = now
|
|
146
138
|
|
|
147
139
|
if bucket.tokens >= 1 then
|
|
@@ -152,38 +144,38 @@ local function checkRate (player: Player, reg: Registration): boolean
|
|
|
152
144
|
return false
|
|
153
145
|
end
|
|
154
146
|
|
|
155
|
-
-- Public
|
|
147
|
+
-- Public --------------------------------------------------------------
|
|
156
148
|
|
|
157
149
|
local Gate = {}
|
|
158
150
|
|
|
159
151
|
-- Override the max recursion depth for NaN/inf scanning. Default 16. Range: 4–32.
|
|
160
|
-
function Gate.setMaxDepth
|
|
152
|
+
function Gate.setMaxDepth(depth: number): ()
|
|
161
153
|
if depth < 4 or depth > 32 then
|
|
162
|
-
error
|
|
154
|
+
error(`[Lync] Gate max depth must be 4–32, got {depth}`)
|
|
163
155
|
end
|
|
164
156
|
_maxDepth = depth
|
|
165
157
|
end
|
|
166
158
|
|
|
167
|
-
function Gate.getMaxDepth
|
|
159
|
+
function Gate.getMaxDepth(): number
|
|
168
160
|
return _maxDepth
|
|
169
161
|
end
|
|
170
162
|
|
|
171
|
-
function Gate.check
|
|
172
|
-
if not checkRate
|
|
173
|
-
fireDrop
|
|
163
|
+
function Gate.check(value: any, reg: Registration, player: Player): boolean
|
|
164
|
+
if not checkRate(player, reg) then
|
|
165
|
+
fireDrop(player, "rate", reg.name, nil)
|
|
174
166
|
return false
|
|
175
167
|
end
|
|
176
168
|
|
|
177
|
-
if not isClean
|
|
178
|
-
fireDrop
|
|
169
|
+
if not isClean(value, 1) then
|
|
170
|
+
fireDrop(player, "nan", reg.name, value)
|
|
179
171
|
return false
|
|
180
172
|
end
|
|
181
173
|
|
|
182
174
|
local validate = reg.validate
|
|
183
175
|
if validate then
|
|
184
|
-
local ok, reason = validate
|
|
176
|
+
local ok, reason = validate(value, player)
|
|
185
177
|
if not ok then
|
|
186
|
-
fireDrop
|
|
178
|
+
fireDrop(player, reason or "validate", reg.name, value)
|
|
187
179
|
return false
|
|
188
180
|
end
|
|
189
181
|
end
|
|
@@ -191,32 +183,32 @@ function Gate.check (value: any, reg: Registration, player: Player): boolean
|
|
|
191
183
|
return true
|
|
192
184
|
end
|
|
193
185
|
|
|
194
|
-
function Gate.onDrop
|
|
186
|
+
function Gate.onDrop(
|
|
195
187
|
callback: (player: Player, reason: string, packetName: string, data: any?) -> ()
|
|
196
188
|
): () -> ()
|
|
197
|
-
table.insert
|
|
189
|
+
table.insert(_dropHandlers, callback)
|
|
198
190
|
|
|
199
|
-
return function
|
|
200
|
-
local idx = table.find
|
|
191
|
+
return function(): ()
|
|
192
|
+
local idx = table.find(_dropHandlers, callback)
|
|
201
193
|
if idx then
|
|
202
|
-
table.remove
|
|
194
|
+
table.remove(_dropHandlers, idx)
|
|
203
195
|
end
|
|
204
196
|
end
|
|
205
197
|
end
|
|
206
198
|
|
|
207
199
|
-- Fires drop handlers without validation. Used by Reader for byte-budget violations.
|
|
208
|
-
function Gate.reportDrop
|
|
200
|
+
function Gate.reportDrop(player: Player?, reason: string, packetName: string, data: any?): ()
|
|
209
201
|
if player and #_dropHandlers > 0 then
|
|
210
|
-
fireDrop
|
|
202
|
+
fireDrop(player, reason, packetName, data)
|
|
211
203
|
end
|
|
212
204
|
end
|
|
213
205
|
|
|
214
|
-
function Gate.onPlayerAdded
|
|
206
|
+
function Gate.onPlayerAdded(player: Player): ()
|
|
215
207
|
_buckets[player] = {}
|
|
216
208
|
end
|
|
217
209
|
|
|
218
|
-
function Gate.onPlayerRemoved
|
|
210
|
+
function Gate.onPlayerRemoved(player: Player): ()
|
|
219
211
|
_buckets[player] = nil
|
|
220
212
|
end
|
|
221
213
|
|
|
222
|
-
return table.freeze
|
|
214
|
+
return table.freeze(Gate)
|
|
@@ -2,49 +2,49 @@
|
|
|
2
2
|
--!optimize 2
|
|
3
3
|
-- Deserializes incoming buffers and routes by registration kind.
|
|
4
4
|
|
|
5
|
-
local Baseline = require
|
|
6
|
-
local Gate = require
|
|
7
|
-
local Middleware = require
|
|
8
|
-
local Registry = require
|
|
5
|
+
local Baseline = require(script.Parent.Parent.internal.Baseline)
|
|
6
|
+
local Gate = require(script.Parent.Gate)
|
|
7
|
+
local Middleware = require(script.Parent.Parent.internal.Middleware)
|
|
8
|
+
local Registry = require(script.Parent.Parent.internal.Registry)
|
|
9
9
|
|
|
10
10
|
local registryGet = Registry.get
|
|
11
11
|
local gateCheck = Gate.check
|
|
12
12
|
local gateReportDrop = Gate.reportDrop
|
|
13
13
|
|
|
14
|
-
-- Constants
|
|
14
|
+
-- Constants -----------------------------------------------------------
|
|
15
15
|
|
|
16
16
|
local KIND_PACKET = Registry.KIND_PACKET
|
|
17
17
|
local KIND_REQUEST = Registry.KIND_REQUEST
|
|
18
18
|
|
|
19
19
|
local MAX_INCOMING = 65536
|
|
20
20
|
|
|
21
|
-
-- Public
|
|
21
|
+
-- Public --------------------------------------------------------------
|
|
22
22
|
|
|
23
23
|
local Reader = {}
|
|
24
24
|
|
|
25
25
|
-- Shared incoming-data validation used by both Client and Server receive.
|
|
26
|
-
function Reader.decodeIncoming
|
|
27
|
-
if typeof
|
|
26
|
+
function Reader.decodeIncoming(data: any): buffer?
|
|
27
|
+
if typeof(data) ~= "buffer" then
|
|
28
28
|
return nil
|
|
29
29
|
end
|
|
30
|
-
if buffer.len
|
|
30
|
+
if buffer.len(data) > MAX_INCOMING then
|
|
31
31
|
return nil
|
|
32
32
|
end
|
|
33
33
|
return data
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
-- Callers wrap this in pcall. Any throw aborts the entire buffer from this source.
|
|
37
|
-
function Reader.process
|
|
37
|
+
function Reader.process(
|
|
38
38
|
incoming: buffer,
|
|
39
39
|
refs: { Instance }?,
|
|
40
40
|
player: Player?,
|
|
41
41
|
startOffset: number
|
|
42
42
|
): ()
|
|
43
|
-
local length = buffer.len
|
|
43
|
+
local length = buffer.len(incoming)
|
|
44
44
|
local pos = startOffset
|
|
45
45
|
|
|
46
46
|
if Baseline.hasDelta then
|
|
47
|
-
Baseline.setReadKey
|
|
47
|
+
Baseline.setReadKey(player or false)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
-- Cache flags once per frame-buffer, not per packet
|
|
@@ -52,12 +52,12 @@ function Reader.process (
|
|
|
52
52
|
local isServer = player ~= nil
|
|
53
53
|
|
|
54
54
|
while pos < length do
|
|
55
|
-
local id = buffer.readu8
|
|
55
|
+
local id = buffer.readu8(incoming, pos)
|
|
56
56
|
pos += 1
|
|
57
57
|
|
|
58
|
-
local reg = registryGet
|
|
58
|
+
local reg = registryGet(id)
|
|
59
59
|
if not reg then
|
|
60
|
-
warn
|
|
60
|
+
warn(`[Lync] Unknown packet ID {id}`)
|
|
61
61
|
break
|
|
62
62
|
end
|
|
63
63
|
|
|
@@ -68,7 +68,7 @@ function Reader.process (
|
|
|
68
68
|
break
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
-
local count = buffer.readu16
|
|
71
|
+
local count = buffer.readu16(incoming, pos)
|
|
72
72
|
pos += 2
|
|
73
73
|
|
|
74
74
|
if count == 0 then
|
|
@@ -87,9 +87,9 @@ function Reader.process (
|
|
|
87
87
|
break
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
local value, consumed = codec.read
|
|
90
|
+
local value, consumed = codec.read(incoming, pos, refs)
|
|
91
91
|
pos += consumed
|
|
92
|
-
signal:fire
|
|
92
|
+
signal:fire(value, player)
|
|
93
93
|
end
|
|
94
94
|
else
|
|
95
95
|
-- Cold path: gate and/or middleware and/or byte budget active
|
|
@@ -102,25 +102,25 @@ function Reader.process (
|
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
if maxBytes and (pos - batchStart) > maxBytes then
|
|
105
|
-
gateReportDrop
|
|
105
|
+
gateReportDrop(player, "size", reg.name, nil)
|
|
106
106
|
break
|
|
107
107
|
end
|
|
108
108
|
|
|
109
|
-
local value, consumed = codec.read
|
|
109
|
+
local value, consumed = codec.read(incoming, pos, refs)
|
|
110
110
|
pos += consumed
|
|
111
111
|
|
|
112
|
-
if needsGate and not gateCheck
|
|
112
|
+
if needsGate and not gateCheck(value, reg, player :: Player) then
|
|
113
113
|
continue
|
|
114
114
|
end
|
|
115
115
|
|
|
116
116
|
if hasReceiveMiddleware then
|
|
117
|
-
local final = Middleware.runReceive
|
|
117
|
+
local final = Middleware.runReceive(value, baseName, player)
|
|
118
118
|
if final == nil then
|
|
119
119
|
continue
|
|
120
120
|
end
|
|
121
|
-
signal:fire
|
|
121
|
+
signal:fire(final, player)
|
|
122
122
|
else
|
|
123
|
-
signal:fire
|
|
123
|
+
signal:fire(value, player)
|
|
124
124
|
end
|
|
125
125
|
end
|
|
126
126
|
end
|
|
@@ -133,29 +133,29 @@ function Reader.process (
|
|
|
133
133
|
local queryCodec = reg.codec
|
|
134
134
|
local querySignal = reg.signal
|
|
135
135
|
|
|
136
|
-
local correlationId = buffer.readu16
|
|
137
|
-
local status = buffer.readu8
|
|
136
|
+
local correlationId = buffer.readu16(incoming, pos)
|
|
137
|
+
local status = buffer.readu8(incoming, pos + 2)
|
|
138
138
|
pos += 3
|
|
139
139
|
|
|
140
140
|
if status == 1 then
|
|
141
141
|
if kind == KIND_REQUEST then
|
|
142
|
-
querySignal:fire
|
|
142
|
+
querySignal:fire(nil, player, correlationId)
|
|
143
143
|
else
|
|
144
|
-
querySignal:fire
|
|
144
|
+
querySignal:fire(nil, correlationId)
|
|
145
145
|
end
|
|
146
146
|
continue
|
|
147
147
|
end
|
|
148
148
|
|
|
149
|
-
local value, consumed = queryCodec.read
|
|
149
|
+
local value, consumed = queryCodec.read(incoming, pos, refs)
|
|
150
150
|
pos += consumed
|
|
151
151
|
|
|
152
|
-
if isServer and reg.needsGate and not gateCheck
|
|
152
|
+
if isServer and reg.needsGate and not gateCheck(value, reg, player :: Player) then
|
|
153
153
|
continue
|
|
154
154
|
end
|
|
155
155
|
|
|
156
156
|
local final: any
|
|
157
157
|
if hasReceiveMiddleware then
|
|
158
|
-
final = Middleware.runReceive
|
|
158
|
+
final = Middleware.runReceive(value, reg.baseName, player)
|
|
159
159
|
if final == nil then
|
|
160
160
|
continue
|
|
161
161
|
end
|
|
@@ -164,12 +164,12 @@ function Reader.process (
|
|
|
164
164
|
end
|
|
165
165
|
|
|
166
166
|
if kind == KIND_REQUEST then
|
|
167
|
-
querySignal:fire
|
|
167
|
+
querySignal:fire(final, player, correlationId)
|
|
168
168
|
else
|
|
169
|
-
querySignal:fire
|
|
169
|
+
querySignal:fire(final, correlationId)
|
|
170
170
|
end
|
|
171
171
|
end
|
|
172
172
|
end
|
|
173
173
|
end
|
|
174
174
|
|
|
175
|
-
return table.freeze
|
|
175
|
+
return table.freeze(Reader)
|