Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## Checklist

### Testing
- [ ] I compiled the code successfully on supported platforms.
- [ ] I manually verified the new functionality or bug fix.

### Code Style
- [ ] I followed the project’s coding conventions (indentation, naming, comments).
- [ ] I ensured no warnings or errors are introduced.

### AI Usage
- [ ] No AI assistance was used.
- [ ] AI was used for **partial assistance** (e.g., generating boilerplate, refactoring).
- [ ] AI was used for **full generation** (e.g., drafting entire functions or files).
- [ ] I reviewed and validated all AI-generated code.

*(Please tick the appropriate AI usage option above.)*

---

## Description
Explain the changes you made and why. Include context, motivation, and any relevant background.

## Related Issues
Link to issues or discussions this PR addresses.

## Additional Notes
Add screenshots, benchmarks, or documentation updates if applicable.
32 changes: 32 additions & 0 deletions .github/workflows/pre-checklist.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: PR Checklist

on:
pull_request:
branches:
- master

jobs:
checklist:
name: Verify PR Checklist
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Validate PR description
uses: actions/github-script@v7
with:
script: |
const body = context.payload.pull_request.body || "";
const checklist = [
"Did you compile the code successfully",
"Did you run the relevant test suite",
"Did you manually verify functionality",
"Did you follow coding conventions",
"Did you run formatting tools",
"AI assistance: No / Partial / Full"
];
let missing = checklist.filter(item => !body.includes(item));
if (missing.length > 0) {
core.setFailed(`PR description missing checklist items: ${missing.join(", ")}`);
}
16 changes: 16 additions & 0 deletions lib/luadata.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ static inline void *luadata_checkbounds(lua_State *L, int ix, luadata_t *data, l
return (LUADATA_TOPTR(data) + offset);
}

void *luadata_checkbuffer(lua_State *L, int first_arg, size_t *len_out)
{
luadata_t *d = luadata_check(L, first_arg);
lua_Integer off = luaL_checkinteger(L, first_arg+1);
lua_Integer lval = luaL_checkinteger(L, first_arg+2);
size_t len = (size_t)lval;

void *ptr = luadata_checkbounds(L, first_arg+1, d, off, lval);

if (len_out)
*len_out = len;

return ptr;
}
EXPORT_SYMBOL(luadata_checkbuffer);

#define luadata_checkwritable(L, data) luaL_argcheck((L), !((data)->opt & LUADATA_OPT_READONLY), 1, "read only")

#define LUADATA_NEWINT_GETTER(T) \
Expand Down
116 changes: 91 additions & 25 deletions lib/luasocket.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@

#include <lunatik.h>

#include "luadata.h"

#define luasocket_msgaddr(msg, addr, size) \
do { \
msg.msg_namelen = size; \
Expand Down Expand Up @@ -109,30 +111,51 @@ LUNATIK_PRIVATECHECKER(luasocket_check, struct socket *);
#define luasocket_setmsg(m) memset(&(m), 0, sizeof(m))

/***
* Sends a message through the socket.
* Sends data through the socket.
*
* For connection-oriented sockets (`SOCK_STREAM`), `addr` and `port` are usually omitted
* as the connection is already established.
* For connectionless sockets (`SOCK_DGRAM`), `addr` and `port` (if applicable for the
* address family) specify the destination.
*
* @function send
* @tparam string message The string message to send.
* @tparam string|data message_or_data
* Either:
* - A Lua string containing the bytes to send (simple case), or
* - A `data` object representing a raw memory block.
* @tparam[opt] integer offset
* When the second argument is a `data` object, the byte offset into the
* underlying buffer (0-indexed) where sending begins.
* @tparam[opt] integer length
* When the second argument is a `data` object, the number of bytes to send
* starting at `offset`.
* @tparam[opt] integer|string addr The destination address.
*
* - For `AF_INET` (IPv4) sockets: An integer representing the IPv4 address (e.g., from `net.aton()`).
* - For other address families (e.g., `AF_PACKET`): A packed string representing the destination address
* (e.g., MAC address for `AF_PACKET`). The exact format depends on the family.
* @tparam[opt] integer port The destination port number (required if `addr` is an IPv4 address for `AF_INET`).
* - For `AF_INET` (IPv4) sockets: An integer representing the IPv4 address
* (e.g., from `net.aton()`).
* - For other address families (e.g., `AF_PACKET`): A packed string
* representing the destination address (e.g., MAC address for `AF_PACKET`).
* The exact format depends on the family.
* @tparam[opt] integer port The destination port number (required if `addr` is
* an IPv4 address for `AF_INET`).
* @treturn integer The number of bytes sent.
* @raise Error if the send operation fails or if address parameters are incorrect for the socket type.
* @raise Error if the send operation fails, if address parameters are incorrect
* for the socket type, or if `data_obj`, `offset`, or `length` are invalid.
* @usage
* -- For a connected TCP socket:
* -- For a connected TCP socket with a Lua string:
* local bytes_sent = tcp_conn_sock:send("Hello, server!")
*
* -- For a UDP socket (sending to 192.168.1.100, port 1234):
* -- For a UDP socket (sending to 192.168.1.100, port 1234) with a Lua string:
* local bytes_sent = udp_sock:send("UDP packet", net.aton("192.168.1.100"), 1234)
*
* -- Using a `data` buffer (zero-copy) with a connected TCP socket:
* local data = require "data"
* local buf = data.new(5)
* buf:setstring(0, "qerty")
* local bytes_sent = tcp_conn_sock:send(buf, 0, 5)
*
* @see net.aton
* @see data
*/
static int luasocket_send(lua_State *L)
{
Expand All @@ -143,14 +166,20 @@ static int luasocket_send(lua_State *L)
struct sockaddr_storage addr;
int nargs = lua_gettop(L);
int ret;
int ixadrr = 3;

luasocket_setmsg(msg);

vec.iov_base = (void *)luaL_checklstring(L, 2, &len);
if (lua_type(L, 2) == LUA_TSTRING)
vec.iov_base = (void *)luaL_checklstring(L, 2, &len);
else {
vec.iov_base = luadata_checkbuffer(L, 2, &len);
ixadrr = 5;
}
vec.iov_len = len;

if (unlikely(nargs >= 3)) {
size_t size = luasocket_checkaddr(L, socket, &addr, 3);
if (unlikely(nargs >= ixadrr)) {
size_t size = luasocket_checkaddr(L, socket, &addr, ixadrr);
luasocket_msgaddr(msg, addr, size);
}

Expand All @@ -160,10 +189,22 @@ static int luasocket_send(lua_State *L)
}

/***
* Receives a message from the socket.
* Receives data from the socket.
*
* This function is overloaded and can either return a Lua string with the
* received bytes or write the bytes directly into a data object.
*
* @function receive
* @tparam integer length The maximum number of bytes to receive.
* @tparam integer|data length_or_data
* Either:
* - An integer `length`: maximum number of bytes to receive into a Lua string.
* - A `data` object: buffer into which data will be received.
* @tparam[opt] integer offset
* When the second argument is a `data` object, the byte offset into the
* underlying buffer (0-indexed) where writing will begin.
* @tparam[opt] integer maxlen
* When the second argument is a `data` object, the maximum number of bytes
* to receive into the buffer starting at `offset`.
* @tparam[opt=0] integer flags Optional message flags (e.g., `socket.msg.PEEK`).
* See the `socket.msg` table for available flags. These can be OR'd together.
* @tparam[opt=false] boolean from If `true`, the function also returns the sender's address
Expand All @@ -173,40 +214,65 @@ static int luasocket_send(lua_State *L)
* - For `AF_INET`: An integer representing the IPv4 address (can be converted with `net.ntoa()`).
* - For other families: A packed string representing the sender's address.
* @treturn[opt] integer port If `from` is true and the family is `AF_INET`, the sender's port number.
* @raise Error if the receive operation fails.
* @raise Error if the receive operation fails, or if `data_obj`, `offset`, or
* `maxlen` are invalid.
* @usage
* -- For a connected TCP socket:
* -- String form, connected TCP socket:
* local data = tcp_conn_sock:receive(1024)
* if data then print("Received:", data) end
*
* -- For a UDP socket, getting sender info:
* -- String form, UDP socket with sender info:
* local data, sender_ip_int, sender_port = udp_sock:receive(1500, 0, true)
* if data then print("Received from " .. net.ntoa(sender_ip_int) .. ":" .. sender_port .. ": " .. data) end
* if data then
* print("Received from " .. net.ntoa(sender_ip_int) .. ":" .. sender_port .. ": " .. data)
* end
*
* -- `data` form (zero-copy) with connected TCP socket:
* local data = require "data"
* local buf = data.new(1024)
* local n = tcp_conn_sock:receive(buf, 0, 1024)
* if n > 0 then
* print("Received:", buf:getstring(0, n))
* end
*
* @see socket.msg
* @see net.ntoa
* @see data
*/
static int luasocket_receive(lua_State *L)
{
struct socket *socket = luasocket_check(L, 1);
size_t len = (size_t)luaL_checkinteger(L, 2);
size_t len;
luaL_Buffer B;
struct kvec vec;
struct msghdr msg;
struct sockaddr_storage addr;
int flags = luaL_optinteger(L, 3, 0);
int from = lua_toboolean(L, 4);
int ret;

int ixopt = 3;
luasocket_setmsg(msg);

vec.iov_base = (void *)luaL_buffinitsize(L, &B, len);
vec.iov_len = len;
if (lua_type(L, 2) == LUA_TSTRING) {
len = (size_t)luaL_checkinteger(L, 2);
vec.iov_base = (void *)luaL_buffinitsize(L, &B, len);
}
else {
vec.iov_base = luadata_checkbuffer(L, 2, &len);
ixopt = 5;
}

vec.iov_len = len;
int flags = luaL_optinteger(L, ixopt, 0);
int from = lua_toboolean(L, ixopt + 1);

if (unlikely(from))
luasocket_msgaddr(msg, addr, sizeof(addr));

lunatik_tryret(L, ret, kernel_recvmsg, socket, &msg, &vec, 1, len, flags);
luaL_pushresultsize(&B, ret);

if (ixopt == 5)
lua_pushinteger(L, ret);
else
luaL_pushresultsize(&B, ret);

return unlikely(from) ? luasocket_pushaddr(L, (struct sockaddr_storage *)msg.msg_name) + 1 : 1;
}
Expand Down
Loading