@axpecter/lync 1.5.2 → 2.1.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 +499 -357
- package/package.json +2 -2
- package/src/Types.luau +69 -27
- package/src/api/Group.luau +101 -106
- package/src/api/Packet.luau +191 -157
- package/src/api/Query.luau +223 -282
- package/src/api/Scope.luau +46 -57
- package/src/api/Signal.luau +104 -137
- package/src/codec/Base.luau +69 -20
- package/src/codec/composite/Array.luau +189 -201
- package/src/codec/composite/Map.luau +97 -341
- package/src/codec/composite/Optional.luau +27 -23
- package/src/codec/composite/Shared.luau +96 -65
- package/src/codec/composite/Struct.luau +201 -339
- package/src/codec/composite/Tagged.luau +64 -178
- package/src/codec/composite/Tuple.luau +81 -95
- package/src/codec/datatype/Buffer.luau +49 -21
- package/src/codec/datatype/CFrame.luau +207 -30
- package/src/codec/datatype/Color.luau +21 -11
- package/src/codec/datatype/Instance.luau +29 -19
- package/src/codec/datatype/IntVector.luau +33 -21
- package/src/codec/datatype/NumberRange.luau +19 -10
- package/src/codec/datatype/Ray.luau +26 -22
- package/src/codec/datatype/Rect.luau +20 -16
- package/src/codec/datatype/Region.luau +51 -45
- package/src/codec/datatype/Sequence.luau +64 -56
- package/src/codec/datatype/String.luau +89 -56
- package/src/codec/datatype/UDim.luau +40 -22
- package/src/codec/datatype/Vector.luau +181 -17
- package/src/codec/meta/Auto.luau +295 -253
- package/src/codec/meta/Bitfield.luau +104 -148
- package/src/codec/meta/Custom.luau +8 -4
- package/src/codec/meta/Enum.luau +29 -57
- package/src/codec/meta/Float.luau +64 -0
- package/src/codec/meta/Nothing.luau +9 -5
- package/src/codec/meta/Unknown.luau +44 -35
- package/src/codec/primitive/Bool.luau +16 -26
- package/src/codec/primitive/Float16.luau +58 -64
- package/src/codec/primitive/Int.luau +68 -0
- package/src/codec/primitive/Number.luau +21 -43
- package/src/codec/primitive/Varint.luau +67 -46
- package/src/index.d.ts +249 -306
- package/src/init.luau +332 -173
- package/src/internal/Baseline.luau +29 -24
- package/src/internal/Channel.luau +346 -161
- package/src/internal/Middleware.luau +60 -85
- package/src/internal/Pool.luau +19 -30
- package/src/internal/Registry.luau +61 -101
- package/src/transport/Bridge.luau +64 -43
- package/src/transport/Client.luau +60 -117
- package/src/transport/Gate.luau +282 -133
- package/src/transport/Reader.luau +159 -100
- package/src/transport/Server.luau +147 -292
- package/src/api/Namespace.luau +0 -251
- package/src/codec/meta/Quantized.luau +0 -174
|
@@ -1,172 +1,231 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!optimize 2
|
|
3
|
-
-- Deserializes incoming buffers
|
|
3
|
+
-- Deserializes incoming buffers with MSB batch dispatch.
|
|
4
4
|
|
|
5
5
|
local Baseline = require(script.Parent.Parent.internal.Baseline)
|
|
6
|
-
local
|
|
6
|
+
local Channel = require(script.Parent.Parent.internal.Channel)
|
|
7
|
+
local Gate = require(script.Parent.Parent.transport.Gate)
|
|
7
8
|
local Middleware = require(script.Parent.Parent.internal.Middleware)
|
|
8
9
|
local Registry = require(script.Parent.Parent.internal.Registry)
|
|
9
|
-
|
|
10
|
-
local
|
|
11
|
-
local gateCheck = Gate.check
|
|
12
|
-
local gateReportDrop = Gate.reportDrop
|
|
10
|
+
local Types = require(script.Parent.Parent.Types)
|
|
11
|
+
local Varint = require(script.Parent.Parent.codec.primitive.Varint)
|
|
13
12
|
|
|
14
13
|
-- Constants -----------------------------------------------------------
|
|
15
14
|
|
|
16
15
|
local KIND_PACKET = Registry.KIND_PACKET
|
|
17
16
|
local KIND_REQUEST = Registry.KIND_REQUEST
|
|
17
|
+
local KIND_RESPONSE = Registry.KIND_RESPONSE
|
|
18
|
+
|
|
19
|
+
-- Private -------------------------------------------------------------
|
|
18
20
|
|
|
19
|
-
local
|
|
21
|
+
local band = bit32.band
|
|
22
|
+
local readu8 = buffer.readu8
|
|
20
23
|
|
|
21
24
|
-- Public --------------------------------------------------------------
|
|
22
25
|
|
|
23
26
|
local Reader = {}
|
|
24
27
|
|
|
25
|
-
-- Shared incoming-data validation used by both Client and Server receive.
|
|
26
28
|
function Reader.decodeIncoming(data: any): buffer?
|
|
27
29
|
if typeof(data) ~= "buffer" then
|
|
28
30
|
return nil
|
|
29
31
|
end
|
|
30
|
-
|
|
32
|
+
local b = data :: buffer
|
|
33
|
+
local len = buffer.len(b)
|
|
34
|
+
if len == 0 or len > 65536 then
|
|
31
35
|
return nil
|
|
32
36
|
end
|
|
33
|
-
return
|
|
37
|
+
return b
|
|
34
38
|
end
|
|
35
39
|
|
|
36
|
-
-- Callers wrap this in pcall. Any throw aborts the entire buffer from this source.
|
|
37
40
|
function Reader.process(
|
|
38
41
|
incoming: buffer,
|
|
42
|
+
incomingLen: number,
|
|
39
43
|
refs: { Instance }?,
|
|
40
44
|
player: Player?,
|
|
41
|
-
|
|
45
|
+
isServer: boolean
|
|
42
46
|
): ()
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if Baseline.hasDelta then
|
|
47
|
+
-- Set baseline read key for delta codecs
|
|
48
|
+
if Baseline.hasDelta() then
|
|
47
49
|
Baseline.setReadKey(player or false)
|
|
48
50
|
end
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
local
|
|
52
|
-
local
|
|
52
|
+
local statsEnabled = Channel.statsEnabled()
|
|
53
|
+
local hasRecvHooks = Middleware.hasReceiveHooks()
|
|
54
|
+
local pos = 0
|
|
53
55
|
|
|
54
|
-
while pos <
|
|
55
|
-
local
|
|
56
|
+
while pos < incomingLen do
|
|
57
|
+
local raw = readu8(incoming, pos)
|
|
56
58
|
pos += 1
|
|
57
59
|
|
|
58
|
-
local
|
|
60
|
+
local msb = band(raw, 0x80) ~= 0
|
|
61
|
+
local id = band(raw, 0x7F)
|
|
62
|
+
|
|
63
|
+
local reg = Registry.get(id)
|
|
59
64
|
if not reg then
|
|
60
|
-
warn(`[Lync]
|
|
65
|
+
warn(`[Lync] Reader: unknown packet ID {id} at byte {pos - 1}`)
|
|
61
66
|
break
|
|
62
67
|
end
|
|
63
68
|
|
|
64
|
-
|
|
69
|
+
if reg.kind == KIND_PACKET then
|
|
70
|
+
local count: number
|
|
65
71
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
if msb then
|
|
73
|
+
count = 1
|
|
74
|
+
else
|
|
75
|
+
count = buffer.readu16(incoming, pos)
|
|
76
|
+
pos += 2
|
|
69
77
|
end
|
|
70
78
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
-- Read timestamp if this packet has one configured
|
|
80
|
+
local tsMode = reg.timestampMode
|
|
81
|
+
local tsValue: any = nil
|
|
82
|
+
|
|
83
|
+
if tsMode == Channel.TS_FRAME then
|
|
84
|
+
tsValue = readu8(incoming, pos)
|
|
85
|
+
pos += 1
|
|
86
|
+
elseif tsMode == Channel.TS_OFFSET then
|
|
87
|
+
tsValue = buffer.readu16(incoming, pos)
|
|
88
|
+
pos += 2
|
|
89
|
+
elseif tsMode == Channel.TS_FULL then
|
|
90
|
+
tsValue = buffer.readf64(incoming, pos)
|
|
91
|
+
pos += 8
|
|
76
92
|
end
|
|
77
93
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
-- Hot path: no gate, no middleware, no byte budget
|
|
84
|
-
if not (isServer and reg.needsGate) and not hasReceiveMiddleware and not maxBytes then
|
|
85
|
-
for _ = 1, count do
|
|
86
|
-
if pos >= length then
|
|
87
|
-
break
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
local value, consumed = codec.read(incoming, pos, refs)
|
|
91
|
-
pos += consumed
|
|
92
|
-
signal:fire(value, player)
|
|
94
|
+
-- Prevent a malicious count from reading past the buffer
|
|
95
|
+
if reg.codec._size then
|
|
96
|
+
local maxItems = (incomingLen - pos) // (reg.codec._size :: number)
|
|
97
|
+
if count > maxItems then
|
|
98
|
+
count = maxItems
|
|
93
99
|
end
|
|
94
|
-
|
|
95
|
-
-- Cold path: gate and/or middleware and/or byte budget active
|
|
96
|
-
local baseName = reg.baseName
|
|
97
|
-
local needsGate = isServer and reg.needsGate
|
|
100
|
+
end
|
|
98
101
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
end
|
|
102
|
+
for _ = 1, count do
|
|
103
|
+
local value, consumed = reg.codec.read(incoming, pos, refs)
|
|
104
|
+
pos += consumed
|
|
103
105
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
if isServer and reg.needsGate then
|
|
107
|
+
if player and not Gate.checkGlobalRateLimit(player) then
|
|
108
|
+
if statsEnabled then
|
|
109
|
+
reg.drops += 1
|
|
110
|
+
end
|
|
111
|
+
Middleware.fireDrop(player :: Player, "rate", reg.name, value)
|
|
112
|
+
continue
|
|
107
113
|
end
|
|
108
114
|
|
|
109
|
-
|
|
110
|
-
|
|
115
|
+
if player and not Gate.checkRateLimit(reg, player) then
|
|
116
|
+
if statsEnabled then
|
|
117
|
+
reg.drops += 1
|
|
118
|
+
end
|
|
119
|
+
Middleware.fireDrop(player :: Player, "rate", reg.name, value)
|
|
120
|
+
continue
|
|
121
|
+
end
|
|
111
122
|
|
|
112
|
-
|
|
123
|
+
local ok, reason = Gate.validate(value, reg.codec)
|
|
124
|
+
if not ok then
|
|
125
|
+
if statsEnabled then
|
|
126
|
+
reg.drops += 1
|
|
127
|
+
end
|
|
128
|
+
if player then
|
|
129
|
+
Middleware.fireDrop(player, "validation", reg.name, value)
|
|
130
|
+
end
|
|
113
131
|
continue
|
|
114
132
|
end
|
|
115
133
|
|
|
116
|
-
if
|
|
117
|
-
local
|
|
118
|
-
if
|
|
134
|
+
if reg.validate then
|
|
135
|
+
local valid, validReason = reg.validate(value, player :: Player)
|
|
136
|
+
if not valid then
|
|
137
|
+
if statsEnabled then
|
|
138
|
+
reg.drops += 1
|
|
139
|
+
end
|
|
140
|
+
Middleware.fireDrop(
|
|
141
|
+
player :: Player,
|
|
142
|
+
validReason or "validate",
|
|
143
|
+
reg.name,
|
|
144
|
+
value
|
|
145
|
+
)
|
|
119
146
|
continue
|
|
120
147
|
end
|
|
121
|
-
signal:fire(final, player)
|
|
122
|
-
else
|
|
123
|
-
signal:fire(value, player)
|
|
124
148
|
end
|
|
125
149
|
end
|
|
126
|
-
end
|
|
127
|
-
else
|
|
128
|
-
-- Query frame: id(u8, already read) + correlationId(u16) + status(u8) = 3 more bytes
|
|
129
|
-
if pos + 3 > length then
|
|
130
|
-
break
|
|
131
|
-
end
|
|
132
150
|
|
|
133
|
-
|
|
134
|
-
|
|
151
|
+
if hasRecvHooks then
|
|
152
|
+
value = Middleware.runReceive(value, reg.name, player)
|
|
153
|
+
end
|
|
135
154
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
155
|
+
if statsEnabled then
|
|
156
|
+
reg.recvFires += 1
|
|
157
|
+
end
|
|
139
158
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
querySignal:fire(nil, player, correlationId)
|
|
159
|
+
if tsValue ~= nil then
|
|
160
|
+
reg.signal:fire(value, player, tsValue)
|
|
143
161
|
else
|
|
144
|
-
|
|
162
|
+
reg.signal:fire(value, player)
|
|
145
163
|
end
|
|
146
|
-
continue
|
|
147
164
|
end
|
|
148
165
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if isServer and reg.needsGate and not gateCheck(value, reg, player :: Player) then
|
|
153
|
-
continue
|
|
166
|
+
if statsEnabled then
|
|
167
|
+
-- Approximate: counts all bytes consumed in this frame, not just payload
|
|
168
|
+
reg.bytesReceived += pos
|
|
154
169
|
end
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
elseif reg.kind == KIND_REQUEST or reg.kind == KIND_RESPONSE then
|
|
171
|
+
-- MSB distinguishes nil responses (no payload) from data responses
|
|
172
|
+
local corrId, corrBytes = Varint.read(incoming, pos)
|
|
173
|
+
pos += corrBytes
|
|
174
|
+
|
|
175
|
+
if msb then
|
|
176
|
+
if statsEnabled then
|
|
177
|
+
reg.recvFires += 1
|
|
161
178
|
end
|
|
179
|
+
reg.signal:fire(nil, player, corrId)
|
|
162
180
|
else
|
|
163
|
-
|
|
164
|
-
|
|
181
|
+
local value, consumed = reg.codec.read(incoming, pos, refs)
|
|
182
|
+
pos += consumed
|
|
165
183
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
184
|
+
if isServer and reg.needsGate and reg.kind == KIND_REQUEST then
|
|
185
|
+
if player and not Gate.checkRateLimit(reg, player) then
|
|
186
|
+
if statsEnabled then
|
|
187
|
+
reg.drops += 1
|
|
188
|
+
end
|
|
189
|
+
Middleware.fireDrop(player :: Player, "rate", reg.name, value)
|
|
190
|
+
continue
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
local ok, reason = Gate.validate(value, reg.codec)
|
|
194
|
+
if not ok then
|
|
195
|
+
if statsEnabled then
|
|
196
|
+
reg.drops += 1
|
|
197
|
+
end
|
|
198
|
+
if player then
|
|
199
|
+
Middleware.fireDrop(player, "validation", reg.name, value)
|
|
200
|
+
end
|
|
201
|
+
continue
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
if reg.validate then
|
|
205
|
+
local valid, validReason = reg.validate(value, player :: Player)
|
|
206
|
+
if not valid then
|
|
207
|
+
if statsEnabled then
|
|
208
|
+
reg.drops += 1
|
|
209
|
+
end
|
|
210
|
+
Middleware.fireDrop(
|
|
211
|
+
player :: Player,
|
|
212
|
+
validReason or "validate",
|
|
213
|
+
reg.name,
|
|
214
|
+
value
|
|
215
|
+
)
|
|
216
|
+
continue
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
if hasRecvHooks then
|
|
222
|
+
value = Middleware.runReceive(value, reg.name, player)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
if statsEnabled then
|
|
226
|
+
reg.recvFires += 1
|
|
227
|
+
end
|
|
228
|
+
reg.signal:fire(value, player, corrId)
|
|
170
229
|
end
|
|
171
230
|
end
|
|
172
231
|
end
|