From 228ca6d97955f3fc132308b40000152e856c043e Mon Sep 17 00:00:00 2001 From: Physic69 Date: Wed, 10 Dec 2025 01:33:51 +0530 Subject: [PATCH 1/6] Implement send(luadata) keeping send(strings) as well as receive --- lib/luadata.c | 16 +++++++ lib/luadata.h | 2 + lib/luasocket.c | 116 +++++++++++++++++++++++++++++++++++++----------- 3 files changed, 109 insertions(+), 25 deletions(-) diff --git a/lib/luadata.c b/lib/luadata.c index a506115e0..18ab39638 100644 --- a/lib/luadata.c +++ b/lib/luadata.c @@ -59,6 +59,22 @@ static inline void *luadata_checkbounds(lua_State *L, int ix, luadata_t *data, l return (void *)(data->ptr + 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") /*** diff --git a/lib/luadata.h b/lib/luadata.h index 92f3d9fa8..5f04b7777 100644 --- a/lib/luadata.h +++ b/lib/luadata.h @@ -15,10 +15,12 @@ LUNATIK_LIB(data); #define LUADATA_OPT_FREE 0x02 #define LUADATA_OPT_KEEP 0x80 + #define luadata_clear(o) (luadata_reset((o), NULL, 0, LUADATA_OPT_KEEP)) lunatik_object_t *luadata_new(lua_State *L); int luadata_reset(lunatik_object_t *object, void *ptr, size_t size, uint8_t opt); +void *luadata_checkbuffer(lua_State *L, int first_arg, size_t *len_out); static inline void luadata_close(lunatik_object_t *object) { diff --git a/lib/luasocket.c b/lib/luasocket.c index b2dc42f85..f5add46e2 100644 --- a/lib/luasocket.c +++ b/lib/luasocket.c @@ -38,6 +38,8 @@ #include +#include "luadata.h" + #define luasocket_msgaddr(msg, addr, size) \ do { \ msg.msg_namelen = size; \ @@ -113,7 +115,7 @@ 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. @@ -121,22 +123,43 @@ LUNATIK_PRIVATECHECKER(luasocket_check, struct socket *); * 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) { @@ -147,14 +170,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); } @@ -164,10 +193,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 @@ -177,40 +218,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; } From 262be3a2eaaeb548174beae370750e3fc23b4846 Mon Sep 17 00:00:00 2001 From: Physic69 <162324988+Physic69@users.noreply.github.com> Date: Wed, 4 Mar 2026 00:02:30 +0530 Subject: [PATCH 2/6] Add PR checklist workflow --- .github/workflows/pre-checklist.yaml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/pre-checklist.yaml diff --git a/.github/workflows/pre-checklist.yaml b/.github/workflows/pre-checklist.yaml new file mode 100644 index 000000000..a13068dc2 --- /dev/null +++ b/.github/workflows/pre-checklist.yaml @@ -0,0 +1,24 @@ +name: PR Checklist + +on: + pull_request: + types: [opened, edited, reopened] + +jobs: + checklist: + runs-on: ubuntu-latest + steps: + - name: Check PR description + uses: actions/github-script@v7 + with: + script: | + const body = context.payload.pull_request.body || ""; + const checklist = [ + "did you test your patch?", + "did you check the code style?", + "did you use AI?" + ]; + let missing = checklist.filter(item => !body.includes(item)); + if (missing.length > 0) { + core.setFailed(`PR description missing checklist items: ${missing.join(", ")}`); + } From 460b2a2b3a0c255f999679d550065f7b6bca74a0 Mon Sep 17 00:00:00 2001 From: Physic69 <162324988+Physic69@users.noreply.github.com> Date: Wed, 4 Mar 2026 00:05:48 +0530 Subject: [PATCH 3/6] Add PR template with checklist and description sections Added a pull request template with a checklist for testing, code style, AI usage, and sections for description, related issues, and additional notes. --- .github/PR_template.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/PR_template.md diff --git a/.github/PR_template.md b/.github/PR_template.md new file mode 100644 index 000000000..897a06682 --- /dev/null +++ b/.github/PR_template.md @@ -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. From 4c809dff624dcd5d1720af313c335b6ff9f47ddd Mon Sep 17 00:00:00 2001 From: Physic69 <162324988+Physic69@users.noreply.github.com> Date: Wed, 4 Mar 2026 00:07:41 +0530 Subject: [PATCH 4/6] Delete .github/PR_template.md --- .github/PR_template.md | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/PR_template.md diff --git a/.github/PR_template.md b/.github/PR_template.md deleted file mode 100644 index 897a06682..000000000 --- a/.github/PR_template.md +++ /dev/null @@ -1,28 +0,0 @@ -## 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. From b8f034f5ca0ee91e8c8e114ea6cbe8db5bd2854d Mon Sep 17 00:00:00 2001 From: Physic69 <162324988+Physic69@users.noreply.github.com> Date: Wed, 4 Mar 2026 00:08:20 +0530 Subject: [PATCH 5/6] Add pull request template for contributors Added a pull request template to guide contributors in providing necessary information and ensuring code quality. --- .github/PULL_REQUEST_TEMPLATE.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..897a06682 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -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. From aa2420897be118f8d622fbd586bc9ede08c79ac4 Mon Sep 17 00:00:00 2001 From: Physic69 <162324988+Physic69@users.noreply.github.com> Date: Wed, 4 Mar 2026 00:11:25 +0530 Subject: [PATCH 6/6] Enhance PR checklist and restrict to master branch Updated PR checklist to include additional validation items and restricted trigger to master branch. --- .github/workflows/pre-checklist.yaml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pre-checklist.yaml b/.github/workflows/pre-checklist.yaml index a13068dc2..d503fe7c7 100644 --- a/.github/workflows/pre-checklist.yaml +++ b/.github/workflows/pre-checklist.yaml @@ -2,21 +2,29 @@ name: PR Checklist on: pull_request: - types: [opened, edited, reopened] + branches: + - master jobs: checklist: + name: Verify PR Checklist runs-on: ubuntu-latest steps: - - name: Check PR description + - 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 test your patch?", - "did you check the code style?", - "did you use AI?" + "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) {