hammerspoon/extensions/socket/libsocket_udp.m

825 lines
33 KiB
Objective-C

#import "socket.h"
#import "CocoaAsyncSocket/GCDAsyncUdpSocket.h"
// Userdata for hs.socket.udp objects
#define getUserData(L, idx) (__bridge HSAsyncUdpSocket *)((asyncSocketUserData *)lua_touserdata(L, idx))->asyncSocket;
static const char *USERDATA_TAG = "hs.socket.udp";
// UDP socket class declaration
@interface HSAsyncUdpSocket : GCDAsyncUdpSocket <GCDAsyncUdpSocketDelegate>
@property int readCallback;
@property int writeCallback;
@property int connectCallback;
@property NSTimeInterval timeout;
@end
// Lua callbacks
static void connectCallback(HSAsyncUdpSocket *asyncUdpSocket) {
mainThreadDispatch(
if (asyncUdpSocket.connectCallback != LUA_NOREF) {
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
_lua_stackguard_entry(skin.L);
[skin pushLuaRef:refTable ref:asyncUdpSocket.connectCallback];
asyncUdpSocket.connectCallback = [skin luaUnref:refTable ref:asyncUdpSocket.connectCallback];
[skin protectedCallAndError:@"hs.socket.udp:connect" nargs:0 nresults:0];
_lua_stackguard_exit(skin.L);
}
);
}
static void writeCallback(HSAsyncUdpSocket *asyncUdpSocket, long tag) {
mainThreadDispatch(
if (asyncUdpSocket.writeCallback != LUA_NOREF) {
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
_lua_stackguard_entry(skin.L);
[skin pushLuaRef:refTable ref:asyncUdpSocket.writeCallback];
[skin pushNSObject: @(tag)];
asyncUdpSocket.writeCallback = [skin luaUnref:refTable ref:asyncUdpSocket.writeCallback];
[skin protectedCallAndError:@"hs.socket.udp:write callback" nargs:1 nresults:0];
_lua_stackguard_exit(skin.L);
}
);
}
static void readCallback(HSAsyncUdpSocket *asyncUdpSocket, NSData *data, NSData *address) {
mainThreadDispatch(
if (asyncUdpSocket.readCallback != LUA_NOREF) {
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
_lua_stackguard_entry(skin.L);
[skin pushLuaRef:refTable ref:asyncUdpSocket.readCallback];
[skin pushNSObject: [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
[skin pushNSObject: address];
[skin protectedCallAndError:@"hs.socket.udp:read callback" nargs:2 nresults:0];
_lua_stackguard_exit(skin.L);
}
);
}
// Delegate implementation
@implementation HSAsyncUdpSocket
- (id)init {
dispatch_queue_t udpDelegateQueue = dispatch_queue_create("udpDelegateQueue", NULL);
self.readCallback = LUA_NOREF;
self.writeCallback = LUA_NOREF;
self.connectCallback = LUA_NOREF;
self.timeout = -1;
return [super initWithDelegate:self delegateQueue:udpDelegateQueue];
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address {
[LuaSkin logDebug:@"UDP socket connected"];
self.userData = DEFAULT;
if (self.connectCallback != LUA_NOREF)
connectCallback(self);
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error {
[LuaSkin logError:[NSString stringWithFormat:@"UDP socket did not connect: %@", [error localizedDescription]]];
mainThreadDispatch(self.connectCallback = [[LuaSkin sharedWithState:NULL] luaUnref:refTable ref:self.connectCallback]);
}
- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError *)error {
[LuaSkin logDebug:[NSString stringWithFormat:@"UDP socket closed: %@", [error localizedDescription]]];
sock.userData = nil;
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag {
[LuaSkin logDebug:@"Data written to UDP socket"];
if (self.writeCallback != LUA_NOREF)
writeCallback(self, tag);
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error {
[LuaSkin logError:[NSString stringWithFormat:@"Data not sent on UDP socket: %@", [error localizedDescription]]];
mainThreadDispatch(self.writeCallback = [[LuaSkin sharedWithState:NULL] luaUnref:refTable ref:self.writeCallback]);
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext {
[LuaSkin logDebug:@"Data read from UDP socket"];
if (self.readCallback != LUA_NOREF)
readCallback(self, data, address);
}
@end
/// hs.socket.udp.new([fn]) -> hs.socket.udp object
/// Constructor
/// Creates an unconnected asynchronous UDP socket object
///
/// Parameters:
/// * fn - An optional [callback function](#setCallback) for reading data from the socket, settable here for convenience
///
/// Returns:
/// * An [`hs.socket.udp`](#new) object
///
static int socketudp_new(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TFUNCTION|LS_TNIL|LS_TOPTIONAL, LS_TBREAK];
HSAsyncUdpSocket *asyncUdpSocket = [[HSAsyncUdpSocket alloc] init];
if (lua_type(L, 1) == LUA_TFUNCTION) {
lua_pushvalue(L, 1);
asyncUdpSocket.readCallback = [skin luaRef:refTable];
}
[skin requireModule:"hs.socket"] ;
for (NSString *field in @[@"udp", @"timeout"])
lua_getfield(skin.L, -1, [field UTF8String]);
asyncUdpSocket.timeout = lua_tonumber(skin.L, -1);
asyncSocketUserData *userData = lua_newuserdata(L, sizeof(asyncSocketUserData));
memset(userData, 0, sizeof(asyncSocketUserData));
userData->asyncSocket = (__bridge_retained void*)asyncUdpSocket;
luaL_getmetatable(L, USERDATA_TAG);
lua_setmetatable(L, -2);
return 1;
}
/// hs.socket.udp:connect(host, port[, fn]) -> self or nil
/// Method
/// Connects an unconnected [`hs.socket.udp`](#new) instance
///
/// Parameters:
/// * host - A string containing the hostname or IP address
/// * port - A port number [1-65535]
/// * fn - An optional single-use callback function to execute after establishing the connection. Receives no parameters
///
/// Returns:
/// * The [`hs.socket.udp`](#new) object or `nil` if an error occured
///
/// Notes:
/// * By design, UDP is a connectionless protocol, and connecting is not needed
/// * Choosing to connect to a specific host/port has the following effect:
/// * You will only be able to send data to the connected host/port
/// * You will only be able to receive data from the connected host/port
/// * You will receive ICMP messages that come from the connected host/port, such as "connection refused"
/// * The actual process of connecting a UDP socket does not result in any communication on the socket. It simply changes the internal state of the socket
/// * You cannot bind a socket after it has been connected
/// * You can only connect a socket once
///
static int socketudp_connect(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING, LS_TNUMBER|LS_TINTEGER, LS_TFUNCTION|LS_TOPTIONAL, LS_TBREAK];
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
NSString *theHost = [skin toNSObjectAtIndex:2];
UInt16 thePort = [[skin toNSObjectAtIndex:3] unsignedShortValue];
NSError *err;
if (lua_type(L, 4) == LUA_TFUNCTION) {
lua_pushvalue(L, 4);
asyncUdpSocket.connectCallback = [skin luaRef:refTable];
}
if (![asyncUdpSocket connectToHost:theHost onPort:thePort error:&err]) {
asyncUdpSocket.connectCallback = [skin luaUnref:refTable ref:asyncUdpSocket.connectCallback];
[LuaSkin logError:[NSString stringWithFormat:@"Unable to connect: %@",
[err localizedDescription]]];
lua_pushnil(L);
return 1;
}
lua_pushvalue(L, 1);
return 1;
}
/// hs.socket.udp:listen(port) -> self or nil
/// Method
/// Binds an unconnected [`hs.socket.udp`](#new) instance to a port for listening
///
/// Parameters:
/// * port - A port number [0-65535]. Ports [1-1023] are privileged. Port 0 allows the OS to select any available port
///
/// Returns:
/// * The [`hs.socket.udp`](#new) object or `nil` if an error occured
///
static int socketudp_listen(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER|LS_TINTEGER, LS_TBREAK];
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
UInt16 thePort = [[skin toNSObjectAtIndex:2] unsignedShortValue];
NSError *err;
if (![asyncUdpSocket bindToPort:thePort error:&err]) {
[LuaSkin logError:[NSString stringWithFormat:@"Unable to bind port: %@",
[err localizedDescription]]];
lua_pushnil(L);
return 1;
}
asyncUdpSocket.userData = SERVER;
lua_pushvalue(L, 1);
return 1;
}
/// hs.socket.udp:close() -> self
/// Method
/// Immediately closes the underlying socket, freeing the [`hs.socket.udp`](#new) instance for reuse. Any pending send operations are discarded
///
/// Parameters:
/// * None
///
/// Returns:
/// * The [`hs.socket.udp`](#new) object
///
static int socketudp_close(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
[asyncUdpSocket close];
lua_pushvalue(L, 1);
return 1;
}
/// hs.socket.udp:pause() -> self
/// Method
/// Suspends reading of packets from the socket. Call one of the receive methods to resume
///
/// Parameters:
/// * None
///
/// Returns:
/// * The [`hs.socket.udp`](#new) object
///
static int socketudp_pause(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
[asyncUdpSocket pauseReceiving];
lua_pushvalue(L, 1);
return 1;
}
static BOOL socketudp_receiveContinuous(lua_State *L, BOOL readContinuous) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TFUNCTION|LS_TOPTIONAL, LS_TBREAK];
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
NSError *err;
if (lua_type(L, 2) == LUA_TFUNCTION) {
asyncUdpSocket.readCallback = [skin luaUnref:refTable ref:asyncUdpSocket.readCallback];
lua_pushvalue(L, 2);
asyncUdpSocket.readCallback = [skin luaRef:refTable];
}
if (asyncUdpSocket.readCallback == LUA_NOREF) {
[LuaSkin logError:@"No callback defined!"];
return false;
}
readContinuous ? [asyncUdpSocket beginReceiving:&err] : [asyncUdpSocket receiveOnce:&err];
if (err) {
[LuaSkin logError:[NSString stringWithFormat:@"Unable to read from UDP socket: %@",
[err localizedDescription]]];
return false;
}
return true;
}
/// hs.socket.udp:receive([fn]) -> self or nil
/// Method
/// Reads packets from the socket as they arrive. Results are passed to the [callback function](#setCallback), which must be set to use this method
///
/// Parameters:
/// * fn - Optionally supply the [read callback](#setCallback) here
///
/// Returns:
/// * The [`hs.socket.udp`](#new) object or `nil` if an error occured
///
/// Notes:
/// * There are two modes of operation for receiving packets: one-at-a-time & continuous
/// * In one-at-a-time mode, you call receiveOne every time you are ready process an incoming UDP packet
/// * Receiving packets one-at-a-time may be better suited for implementing certain state machine code where your state machine may not always be ready to process incoming packets
/// * In continuous mode, the callback is invoked immediately every time incoming udp packets are received
/// * Receiving packets continuously is better suited to real-time streaming applications
/// * You may switch back and forth between one-at-a-time mode and continuous mode
/// * If the socket is currently in one-at-a-time mode, calling this method will switch it to continuous mode
///
static int socketudp_receive(lua_State *L) {
socketudp_receiveContinuous(L, true) ? lua_pushvalue(L, 1) : lua_pushnil(L);
return 1;
}
/// hs.socket.udp:receiveOne([fn]) -> self or nil
/// Method
/// Reads a single packet from the socket. Results are passed to the [callback function](#setCallback), which must be set to use this method
///
/// Parameters:
/// * fn - Optionally supply the [read callback](#setCallback) here
///
/// Returns:
/// * The [`hs.socket.udp`](#new) object or `nil` if an error occured
///
/// Notes:
/// * There are two modes of operation for receiving packets: one-at-a-time & continuous
/// * In one-at-a-time mode, you call receiveOne every time you are ready process an incoming UDP packet
/// * Receiving packets one-at-a-time may be better suited for implementing certain state machine code where your state machine may not always be ready to process incoming packets
/// * In continuous mode, the callback is invoked immediately every time incoming udp packets are received
/// * Receiving packets continuously is better suited to real-time streaming applications
/// * You may switch back and forth between one-at-a-time mode and continuous mode
/// * If the socket is currently in continuous mode, calling this method will switch it to one-at-a-time mode
///
static int socketudp_receiveOne(lua_State *L) {
socketudp_receiveContinuous(L, false) ? lua_pushvalue(L, 1) : lua_pushnil(L);
return 1;
}
/// hs.socket.udp:send(message, host, port[, tag][, fn]) -> self
/// Method
/// Sends a packet to the destination address
///
/// Parameters:
/// * message - A string containing data to be sent on the socket
/// * host - A string containing the hostname or IP address
/// * port - A port number [1-65535]
/// * tag - An optional integer to assist with labeling writes
/// * fn - An optional single-use callback function to execute after sending the packet. Receives the tag parameter
///
/// Returns:
/// * The [`hs.socket.udp`](#new) object
///
/// Notes:
/// * For non-connected sockets, the remote destination is specified for each packet
/// * If the socket has been explicitly connected with [`connect`](#connect), only the message parameter and an optional tag and/or write callback can be supplied
/// * Recall that connecting is optional for a UDP socket
/// * For connected sockets, data can only be sent to the connected address
///
static int socketudp_send(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING, LS_TANY|LS_TOPTIONAL, LS_TANY|LS_TOPTIONAL, LS_TANY|LS_TOPTIONAL, LS_TANY|LS_TOPTIONAL, LS_TBREAK];
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
NSData *sendData = [skin toNSObjectAtIndex:2 withOptions:LS_NSLuaStringAsDataOnly];
if (asyncUdpSocket.isConnected) {
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING, LS_TNUMBER|LS_TINTEGER|LS_TFUNCTION|LS_TNIL|LS_TOPTIONAL, LS_TFUNCTION|LS_TOPTIONAL, LS_TBREAK];
long tag = (lua_type(L, 3) == LUA_TNUMBER) ? lua_tointeger(L, 3) : -1;
if (lua_type(L, 3) == LUA_TFUNCTION) {
lua_pushvalue(L, 3);
asyncUdpSocket.writeCallback = [skin luaRef:refTable];
}
if (lua_type(L, 3) != LUA_TFUNCTION && lua_type(L, 4) == LUA_TFUNCTION) {
lua_pushvalue(L, 4);
asyncUdpSocket.writeCallback = [skin luaRef:refTable];
}
if (sendData) {
[asyncUdpSocket sendData:sendData
withTimeout:asyncUdpSocket.timeout
tag:tag];
}
} else {
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING, LS_TSTRING, LS_TNUMBER|LS_TINTEGER, LS_TNUMBER|LS_TINTEGER|LS_TFUNCTION|LS_TNIL|LS_TOPTIONAL, LS_TFUNCTION|LS_TOPTIONAL, LS_TBREAK];
NSString *theHost = [skin toNSObjectAtIndex:3];
UInt16 thePort = [[skin toNSObjectAtIndex:4] unsignedShortValue];
long tag = (lua_type(L, 5) == LUA_TNUMBER) ? lua_tointeger(L, 5) : -1;
if (lua_type(L, 5) == LUA_TFUNCTION) {
lua_pushvalue(L, 5);
asyncUdpSocket.writeCallback = [skin luaRef:refTable];
}
if (lua_type(L, 5) != LUA_TFUNCTION && lua_type(L, 6) == LUA_TFUNCTION) {
lua_pushvalue(L, 6);
asyncUdpSocket.writeCallback = [skin luaRef:refTable];
}
if (sendData) {
[asyncUdpSocket sendData:sendData
toHost:theHost
port:thePort
withTimeout:asyncUdpSocket.timeout
tag:tag];
}
}
lua_pushvalue(L, 1);
return 1;
}
/// hs.socket.udp:broadcast([flag]) -> self or nil
/// Method
/// Enables broadcasting on the underlying socket
///
/// Parameters:
/// * flag - An optional boolean: `true` to enable broadcasting, `false` to disable it. Defaults to `true`
///
/// Returns:
/// * The [`hs.socket.udp`](#new) object or `nil` if an error occurred
///
/// Notes:
/// * By default, the underlying socket in the OS will not allow you to send broadcast messages
/// * In order to send broadcast messages, you need to enable this functionality in the socket
/// * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is delivered to every host on the network.
/// * The reason this is generally disabled by default (by the OS) is to prevent accidental broadcast messages from flooding the network.
///
static int socketudp_enableBroadcast(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBOOLEAN|LS_TOPTIONAL, LS_TBREAK];
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
BOOL enableFlag = (lua_type(L, 2) == LUA_TBOOLEAN && lua_toboolean(L, 3) == false) ? false : true;
NSError *err;
if (![asyncUdpSocket enableBroadcast:enableFlag error:&err]) {
[LuaSkin logError:[NSString stringWithFormat:@"Unable to enable broadcasting: %@",
[err localizedDescription]]];
lua_pushnil(L);
return 1;
}
lua_pushvalue(L, 1);
return 1;
}
/// hs.socket.udp:reusePort([flag]) -> self or nil
/// Method
/// Enables port reuse on the underlying socket
///
/// Parameters:
/// * flag - An optional boolean: `true` to enable port reuse, `false` to disable it. Defaults to `true`
///
/// Returns:
/// * The [`hs.socket.udp`](#new) object or `nil` if an error occurred
///
/// Notes:
/// * By default, only one socket can be bound to a given IP address+port at a time
/// * To enable multiple processes to simultaneously bind to the same address+port, you need to enable this functionality in the socket
/// * All processes that wish to use the address+port simultaneously must all enable reuse port on the socket bound to that port
/// * Must be called before binding the socket
///
static int socketudp_enableReusePort(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBOOLEAN|LS_TOPTIONAL, LS_TBREAK];
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
BOOL enableFlag = (lua_type(L, 2) == LUA_TBOOLEAN && lua_toboolean(L, 3) == false) ? false : true;
NSError *err;
if (![asyncUdpSocket enableReusePort:enableFlag error:&err]) {
[LuaSkin logError:[NSString stringWithFormat:@"Unable to enable port reuse: %@",
[err localizedDescription]]];
lua_pushnil(L);
return 1;
}
lua_pushvalue(L, 1);
return 1;
}
/// hs.socket.udp:enableIPv(version[, flag]) -> self or nil
/// Method
/// Enables or disables IPv4 or IPv6 on the underlying socket. By default, both are enabled
///
/// Parameters:
/// * version - A number containing the IP version (4 or 6) to enable or disable
/// * flag - A boolean: `true` to enable the chosen IP version, `false` to disable it. Defaults to `true`
///
/// Returns:
/// * The [`hs.socket.udp`](#new) object or `nil` if an error occurred
///
/// Notes:
/// * Must be called before binding the socket. If you want to create an IPv6-only server, do something like:
/// * `hs.socket.udp.new(callback):enableIPv(4, false):listen(port):receive()`
/// * The convenience constructor [`hs.socket.server`](#server) will automatically bind the socket and requires closing and relistening to use this method
///
static int socketudp_enableIPversion(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER|LS_TINTEGER, LS_TBOOLEAN|LS_TOPTIONAL, LS_TBREAK];
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
UInt8 ipVersion = lua_tointeger(L, 2);
BOOL enableFlag = (lua_type(L, 3) == LUA_TBOOLEAN && lua_toboolean(L, 3) == false) ? false : true;
if (ipVersion == 4) {
[asyncUdpSocket setIPv4Enabled:enableFlag];
} else if (ipVersion == 6) {
[asyncUdpSocket setIPv6Enabled:enableFlag];
} else {
[LuaSkin logError:[NSString stringWithFormat:@"Invalid IP version: %hhu", ipVersion]];
lua_pushnil(L);
return 1;
}
lua_pushvalue(L, 1);
return 1;
}
/// hs.socket.udp:preferIPv([version]) -> self
/// Method
/// Sets the preferred IP version: IPv4, IPv6, or neutral (first to resolve)
///
/// Parameters:
/// * version - An optional number containing the IP version to prefer. Anything but 4 or 6 else sets the default neutral behavior
///
/// Returns:
/// * The [`hs.socket.udp`](#new) object
///
/// Notes:
/// * If a DNS lookup returns only IPv4 results, the socket will automatically use IPv4
/// * If a DNS lookup returns only IPv6 results, the socket will automatically use IPv6
/// * If a DNS lookup returns both IPv4 and IPv6 results, then the protocol used depends on the configured preference
///
static int socketudp_preferIPversion(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER|LS_TINTEGER|LS_TOPTIONAL, LS_TBREAK];
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
if (lua_type(L, 2) == LUA_TNUMBER && lua_tointeger(L, 2) == 4) {
[asyncUdpSocket setPreferIPv4];
} else if (lua_type(L, 2) == LUA_TNUMBER && lua_tointeger(L, 2) == 6) {
[asyncUdpSocket setPreferIPv6];
} else {
[asyncUdpSocket setIPVersionNeutral];
}
lua_pushvalue(L, 1);
return 1;
}
/// hs.socket.udp:setBufferSize(size[, version]) -> self
/// Method
/// Sets the maximum size of the buffer that will be allocated for receive operations
///
/// Parameters:
/// * size - An number containing the receive buffer size in bytes
/// * version - An optional number containing the IP version for which to set the buffer size. Anything but 4 or 6 else sets the same size for both
///
/// Returns:
/// * The [`hs.socket.udp`](#new) object
///
/// Notes:
/// * The default maximum size is 9216 bytes
/// * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535
/// * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295
/// * Since the OS notifies us of the size of each received UDP packet, the actual allocated buffer size for each packet is exact
/// * In practice the size of UDP packets is generally much smaller than the max. Most protocols will send and receive packets of only a few bytes, or will set a limit on the size of packets to prevent fragmentation in the IP layer.
/// * If you set the buffer size too small, the sockets API in the OS will silently discard any extra data
///
static int socketudp_setReceiveBufferSize(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER|LS_TINTEGER, LS_TNUMBER|LS_TINTEGER|LS_TOPTIONAL, LS_TBREAK];
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
NSUInteger bufferSize = lua_tointeger(L, 2);
UInt16 IPv4BufferSize = (bufferSize > UINT16_MAX) ? UINT16_MAX : (UInt16)bufferSize;
UInt32 IPv6BufferSize = (bufferSize > UINT32_MAX) ? UINT32_MAX : (UInt32)bufferSize;
if (lua_type(L, 3) == LUA_TNUMBER) {
if (lua_tointeger(L, 3) == 4) {
[asyncUdpSocket setMaxReceiveIPv4BufferSize:IPv4BufferSize];
} else if (lua_tointeger(L, 3) == 6) {
[asyncUdpSocket setMaxReceiveIPv6BufferSize:IPv6BufferSize];
}
} else {
[asyncUdpSocket setMaxReceiveIPv4BufferSize:IPv4BufferSize];
[asyncUdpSocket setMaxReceiveIPv6BufferSize:IPv6BufferSize];
}
lua_pushvalue(L, 1);
return 1;
}
/// hs.socket.udp:setCallback([fn]) -> self
/// Method
/// Sets the read callback for the [`hs.socket.udp`](#new) instance. Must be set to read data from the socket
///
/// Parameters:
/// * fn - An optional callback function to process data read from the socket. `nil` or no argument clears the callback. The callback receives 2 parameters:
/// * data - The data read from the socket as a string
/// * sockaddr - The sending address as a binary socket address structure. See [`parseAddress`](#parseAddress)
///
/// Returns:
/// * The [`hs.socket.udp`](#new) object
static int socketudp_setCallback(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TFUNCTION|LS_TNIL|LS_TOPTIONAL, LS_TBREAK];
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
asyncUdpSocket.readCallback = [skin luaUnref:refTable ref:asyncUdpSocket.readCallback];
if (lua_type(L, 2) == LUA_TFUNCTION) {
lua_pushvalue(L, 2);
asyncUdpSocket.readCallback = [skin luaRef:refTable];
}
lua_pushvalue(L, 1);
return 1;
}
/// hs.socket.udp:setTimeout(timeout) -> self
/// Method
/// Sets the timeout for the socket operations. If the timeout value is negative, the operations will not use a timeout, which is the default
///
/// Parameters:
/// * timeout - A number containing the timeout duration, in seconds
///
/// Returns:
/// * The [`hs.socket.udp`](#new) object
///
static int socketudp_setTimeout(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER, LS_TBREAK];
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
NSTimeInterval timeout = lua_tonumber(L, 2);
asyncUdpSocket.timeout = timeout;
lua_pushvalue(L, 1);
return 1;
}
/// hs.socket.udp:connected() -> bool
/// Method
/// Returns the connection status of the [`hs.socket.udp`](#new) instance
///
/// Parameters:
/// * None
///
/// Returns:
/// * `true` if connected, otherwise `false`
///
/// Notes:
/// * UDP sockets are typically meant to be connectionless
/// * This method will only return `true` if the [`connect`](#connect) method has been explicitly called
///
static int socketudp_connected(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
lua_pushboolean(L, asyncUdpSocket.isConnected);
return 1;
}
/// hs.socket.udp:closed() -> bool
/// Method
/// Returns the closed status of the [`hs.socket.udp`](#new) instance
///
/// Parameters:
/// * None
///
/// Returns:
/// * `true` if closed, otherwise `false`
///
/// Notes:
/// * UDP sockets are typically meant to be connectionless
/// * Sending a packet anywhere, regardless of whether or not the destination receives it, opens the socket until it is explicitly closed
/// * An active listening socket will not be closed, but will not be 'connected' unless the [connect](#connect) method has been called
///
static int socketudp_closed(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
lua_pushboolean(L, asyncUdpSocket.isClosed);
return 1;
}
/// hs.socket.udp:info() -> table
/// Method
/// Returns information on the [`hs.socket.udp`](#new) instance
///
/// Parameters:
/// * None
///
/// Returns:
/// * A table containing the following keys:
/// * connectedAddress - `string` (`sockaddr` struct)
/// * connectedHost - `string`
/// * connectedPort - `number`
/// * isClosed - `boolean`
/// * isConnected - `boolean`
/// * isIPv4 - `boolean`
/// * isIPv4Enabled - `boolean`
/// * isIPv4Preferred - `boolean`
/// * isIPv6 - `boolean`
/// * isIPv6Enabled - `boolean`
/// * isIPv6Preferred - `boolean`
/// * isIPVersionNeutral - `boolean`
/// * localAddress - `string` (`sockaddr` struct)
/// * localAddress_IPv4 - `string` (`sockaddr` struct)
/// * localAddress_IPv6 - `string` (`sockaddr` struct)
/// * localHost - `string`
/// * localHost_IPv4 - `string`
/// * localHost_IPv6 - `string`
/// * localPort - `number`
/// * localPort_IPv4 - `number`
/// * localPort_IPv6 - `number`
/// * maxReceiveIPv4BufferSize - `number`
/// * maxReceiveIPv6BufferSize - `number`
/// * timeout - `number`
/// * userData - `string`
///
static int socketudp_info(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
NSDictionary *info = @{
@"connectedAddress" : asyncUdpSocket.connectedAddress ?: @"",
@"connectedHost" : asyncUdpSocket.connectedHost ?: @"",
@"connectedPort" : @(asyncUdpSocket.connectedPort),
@"isClosed": @(asyncUdpSocket.isClosed),
@"isConnected": @(asyncUdpSocket.isConnected),
@"isIPv4": @(asyncUdpSocket.isIPv4),
@"isIPv4Enabled": @(asyncUdpSocket.isIPv4Enabled),
@"isIPv4Preferred": @(asyncUdpSocket.isIPv4Preferred),
@"isIPv6": @(asyncUdpSocket.isIPv6),
@"isIPv6Enabled": @(asyncUdpSocket.isIPv6Enabled),
@"isIPv6Preferred": @(asyncUdpSocket.isIPv6Preferred),
@"isIPVersionNeutral": @(asyncUdpSocket.isIPVersionNeutral),
@"localAddress": asyncUdpSocket.localAddress ?: @"",
@"localAddress_IPv4": asyncUdpSocket.localAddress_IPv4 ?: @"",
@"localAddress_IPv6": asyncUdpSocket.localAddress_IPv6 ?: @"",
@"localHost": asyncUdpSocket.localHost ?: @"",
@"localHost_IPv4": asyncUdpSocket.localHost_IPv4 ?: @"",
@"localHost_IPv6": asyncUdpSocket.localHost_IPv6 ?: @"",
@"localPort" : @(asyncUdpSocket.localPort),
@"localPort_IPv4" : @(asyncUdpSocket.localPort_IPv4),
@"localPort_IPv6" : @(asyncUdpSocket.localPort_IPv6),
@"maxReceiveIPv4BufferSize" : @(asyncUdpSocket.maxReceiveIPv4BufferSize),
@"maxReceiveIPv6BufferSize" : @(asyncUdpSocket.maxReceiveIPv6BufferSize),
@"timeout": @(asyncUdpSocket.timeout),
@"userData" : asyncUdpSocket.userData ?: @"",
};
[skin pushNSObject:info];
return 1;
}
// Library registration functions
static int userdata_tostring(lua_State* L) {
HSAsyncUdpSocket* asyncUdpSocket = getUserData(L, 1);
BOOL isServer = (asyncUdpSocket.userData == SERVER) ? true : false;
NSString *theHost = isServer ? asyncUdpSocket.localHost : asyncUdpSocket.connectedHost;
UInt16 thePort = isServer ? asyncUdpSocket.localPort : asyncUdpSocket.connectedPort;
lua_pushstring(L, [[NSString stringWithFormat:@"%s: %@:%hu (%p)", USERDATA_TAG, theHost, thePort, lua_topointer(L, 1)] UTF8String]);
return 1;
}
static int userdata_gc(lua_State *L) {
asyncSocketUserData *userData = lua_touserdata(L, 1);
HSAsyncUdpSocket* asyncUdpSocket = (__bridge_transfer HSAsyncUdpSocket *)userData->asyncSocket;
userData->asyncSocket = nil;
LuaSkin *skin = [LuaSkin sharedWithState:L];
[asyncUdpSocket close];
[asyncUdpSocket setDelegate:nil delegateQueue:NULL];
asyncUdpSocket.readCallback = [skin luaUnref:refTable ref:asyncUdpSocket.readCallback];
asyncUdpSocket.writeCallback = [skin luaUnref:refTable ref:asyncUdpSocket.writeCallback];
asyncUdpSocket.connectCallback = [skin luaUnref:refTable ref:asyncUdpSocket.connectCallback];
asyncUdpSocket = nil;
return 0;
}
// Functions for returned object when module loads
static const luaL_Reg moduleLib[] = {
{"new", socketudp_new},
{NULL, NULL} // This must end with an empty struct
};
// Metatable for created objects when _new invoked
static const luaL_Reg userdata_metaLib[] = {
{"connect", socketudp_connect},
{"listen", socketudp_listen},
{"close", socketudp_close},
{"pause", socketudp_pause},
{"receive", socketudp_receive},
{"receiveOne", socketudp_receiveOne},
{"send", socketudp_send},
{"broadcast", socketudp_enableBroadcast},
{"reusePort", socketudp_enableReusePort},
{"enableIPv", socketudp_enableIPversion},
{"preferIPv", socketudp_preferIPversion},
{"setBufferSize", socketudp_setReceiveBufferSize},
{"setCallback", socketudp_setCallback},
{"setTimeout", socketudp_setTimeout},
{"connected", socketudp_connected},
{"closed", socketudp_closed},
{"info", socketudp_info},
{"__tostring", userdata_tostring},
{"__gc", userdata_gc},
{NULL, NULL} // This must end with an empty struct
};
int luaopen_hs_libsocketudp(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
refTable = [skin registerLibrary:USERDATA_TAG functions:moduleLib metaFunctions:meta_gcLib];
[skin registerObject:USERDATA_TAG objectFunctions:userdata_metaLib];
return 1;
}