1
0
mirror of https://github.com/AR2000AR/openComputers_codes.git synced 2025-09-06 21:51:14 +02:00
Files
openComputers_codes/network/lib/socket/udp.lua
2023-11-30 21:02:08 +01:00

177 lines
6.0 KiB
Lua

local ipv4Address = require("network.ipv4.address")
local UDPDatagram = require("network.udp.UDPDatagram")
local network = require("network")
local class = require("libClass2")
---@alias UDPSocketKind
--- | "unconnected"
--- | "connected"
---@class UDPSocket:Object
---@field public kind UDPSocketKind
---@field private _sockname table
---@field private _peername table
---@field private _buffer table
---@field private _timeout number
---@operator call:UDPSocket
---@overload fun(self):UDPSocket
local UDPSocket = class()
---Comment
---@return UDPSocket
function UDPSocket:new()
local o = self.parent()
setmetatable(o, {__index = self})
---@cast o UDPSocket
o._sockname = {"0.0.0.0", 0}
o._peername = {"0.0.0.0", 0}
o.kind = "unconnected"
o._buffer = {}
o._timeout = 0
return o
end
function UDPSocket:close()
network.udp.getInterface():close(self)
end
---Retrieves information about the peer associated with a connected UDP object.\
---Returns the IP address and port number of the peer.\
---Note: It makes no sense to call this method on unconnected objects.
---@return string address,number port
function UDPSocket:getpeername()
return table.unpack(self._peername)
end
---Returns the local address information associated to the object.\
---The method returns a string with local IP address and a number with the port. In case of error, the method returns nil.\
---Note: UDP sockets are not bound to any address until the setsockname or the sendto method is called for the first time (in which case it is bound to an ephemeral port and the wild-card address).
---@return string address,number port
function UDPSocket:getsockname()
return table.unpack(self._sockname)
end
---@param size? number
---@return string?
function UDPSocket:recieve(size)
local datagram = self:receivefrom(size)
return datagram
end
---@return string? datagram, string? fromAddress, number? fromPort
function UDPSocket:receivefrom(size)
--TODO : use the size
if (select(2, self:getsockname()) == 0) then
error("Reciving object before binding to a address/port", 2)
end
local t1 = os.time()
repeat
os.sleep()
until #self._buffer > 0 or (self._timeout > 0 and os.time() - t1 > self._timeout)
if (#self._buffer > 0) then
return table.unpack(table.remove(self._buffer, 1))
end
end
---Sends a datagram to the UDP peer of a connected object.\
---Datagram is a string with the datagram contents. The maximum datagram size for UDP is 64K minus IP layer overhead. However datagrams larger than the link layer packet size will be fragmented, which may deteriorate performance and/or reliability.\
---If successful, the method returns 1. In case of error, the method returns nil followed by an error message.\
---Note: In UDP, the send method never blocks and the only way it can fail is if the underlying transport layer refuses to send a message to the specified address (i.e. no interface accepts the address).
---@param datagram string
---@return number?,string? reason
function UDPSocket:send(datagram)
if (self.kind == "unconnected") then return nil, "Not a connected udp socket" end
return self:sendto(datagram, self:getpeername())
end
---comment
---@param datagram string
---@param ip string
---@param port number
---@return number?,string? reason
function UDPSocket:sendto(datagram, ip, port)
if (select(2, self:getsockname()) == 0) then
self:setsockname('*', 0)
end
local lIP, srcPort = self:getsockname()
local dstIP = ipv4Address.fromString(ip)
local srcIP = ipv4Address.fromString(lIP)
local datagramObject = UDPDatagram(srcPort, port, datagram)
network.udp.getInterface():send(srcIP, dstIP, datagramObject)
return 1
end
---@param address string
---@param port number
---@overload fun(self,address:string)
---@return number? success, string? reason
function UDPSocket:setpeername(address, port)
checkArg(1, address, 'string')
if (self.kind == "connected") then
if (address ~= '*') then
error("Address must be '*'", 2)
end
network.udp.getInterface():connectSocket(self, 0, 0)
self._peername = {"0.0.0.0", 0}
self.kind = "unconnected"
return 1
else
checkArg(2, port, 'number')
local success, reason = network.udp.getInterface():connectSocket(self, ipv4Address.fromString(address), port)
if (success) then
self._peername = {address, port}
self.kind = "connected"
return 1
else
return nil, reason
end
end
end
---@param address string
---@param port number
---@return number? success, string? reason
function UDPSocket:setsockname(address, port)
checkArg(1, address, 'string')
checkArg(2, port, 'number')
local reason
if (not self.kind == "unconnected") then return nil, "Not a unconnected udp socket" end
if (address == '*') then address = "0.0.0.0" end
local _, sockP = self:getsockname()
if (sockP == 0) then
---@diagnostic disable-next-line: cast-local-type
port, reason = network.udp.getInterface():bindSocket(self, ipv4Address.fromString(address), port)
if (port) then
self._sockname = {address, port}
end
return 1, reason
else
return nil, "Socket already bound"
end
end
---Sets options for the UDP object. Options are only needed by low-level or time-critical applications. You should only modify an option if you are sure you need it.
---@param option 'dontroute'|'broadcast'
---@param value? boolean
function UDPSocket:setoption(option, value)
error("NOT IMPLEMENTED", 2)
end
---Set the socket's timeout in second
---@param value number seconds
function UDPSocket:settimeout(value)
checkArg(1, value, 'number')
self._timeout = value * 100
end
---Handle the payload recived by UDPLayer
---@package
---@param from number
---@param to number
---@param udpPacket UDPDatagram
function UDPSocket:payloadHandler(from, to, udpPacket)
table.insert(self._buffer, {udpPacket:payload(), ipv4Address.tostring(from), udpPacket:srcPort()})
end
return UDPSocket