feat(sterni/blipqn): reuse address and socket for multiple Sends

This is achieved by storing the resources we need to acquire for
interacting with the flipdot (socket fd and addrinfo struct) in a
`struct flipdot` that is dynamically allocated and treated as an opaque
pointer object via the BQN FFI.

To make sure these resources are released correctly, we only provide a
lisp style WithFlipdot to the user which takes care of acquiring and
releasing the `struct flipdot`. This works even if an error occurs in
the function the user provides thanks to _defer_. I'm not sure if
calling it _defer_ is right since Go's error handling works differently,
so defer really is deferred execution in a sense which doesn't really
fit what we're doing here. The closest is probably Haskell's bracket,
but that name references it's triadic nature which doesn't fit our
implementation.

Change-Id: Iff65d277a448dbe0c6ac93e816ece5ab6fa10190
Reviewed-on: https://cl.tvl.fyi/c/depot/+/13011
Reviewed-by: sterni <sternenseemann@systemli.org>
Tested-by: BuildkiteCI
This commit is contained in:
sterni 2025-01-15 20:46:32 +01:00
parent 1027e21eee
commit a9e121380b
3 changed files with 78 additions and 34 deletions

View file

@ -1,9 +1,9 @@
#define _DEFAULT_SOURCE // see getnameinfo(3), for NI_MAX*
#define _POSIX_C_SOURCE 200112L // see getaddrinfo(3)
#include <assert.h>
#include <limits.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
@ -19,29 +19,53 @@ int resolve_addr(char *host, char *port, struct addrinfo **addrs) {
return getaddrinfo(host, port, &hints, addrs);
}
// Send given bytes[len] to the host:port via UDP, returns 1 if all bytes
// where sent and no locally detectable errors occurred.
int8_t send_to_flipdot(char *host, in_port_t port_number, uint8_t *bitmap,
size_t bitmap_len) {
struct flipdot {
int sockfd;
struct addrinfo *addrs;
};
// Assumes all pointers in struct flipdot are not NULL which should be the case
// for any struct returned by flipdot_open().
void flipdot_close(struct flipdot *flipdot) {
freeaddrinfo(flipdot->addrs);
close(flipdot->sockfd);
free(flipdot);
}
// Returns NULL if some error occurred. Note that errno isn't necessarily set.
struct flipdot *flipdot_open(char *host, in_port_t port_number) {
char port[NI_MAXSERV];
int sockfd = -1;
ssize_t sent = 0;
struct addrinfo *addrs = NULL;
struct flipdot *flipdot = malloc(sizeof(struct flipdot));
if (flipdot == NULL) goto error;
if (snprintf(port, sizeof port, "%d", port_number) < 0) goto error;
memset(flipdot, 0, sizeof(struct flipdot));
flipdot->sockfd = -1;
flipdot->addrs = NULL;
if (resolve_addr(host, port, &addrs) != 0) goto error;
if (snprintf(port, sizeof(port), "%d", port_number) < 0) goto error;
if (resolve_addr(host, port, &flipdot->addrs) != 0) goto error;
sockfd = socket(addrs->ai_family, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd < 0) goto error;
flipdot->sockfd = socket(flipdot->addrs->ai_family, SOCK_DGRAM, IPPROTO_UDP);
if (flipdot->sockfd < 0) goto error;
sent =
sendto(sockfd, bitmap, bitmap_len, 0, addrs->ai_addr, addrs->ai_addrlen);
if (sent != (ssize_t)bitmap_len) goto error;
return flipdot;
error:
if (addrs != NULL) freeaddrinfo(addrs);
if (sockfd >= 0) close(sockfd);
if (flipdot != NULL) {
if (flipdot->sockfd >= 0) close(flipdot->sockfd);
if (flipdot->addrs != NULL) freeaddrinfo(flipdot->addrs);
free(flipdot);
}
return NULL;
}
// Send given bytes[len] to the given flipdot, returns 1 if all bytes were sent
// and no locally detectable errors occurred, 0 otherwise.
int8_t flipdot_send(struct flipdot *flipdot, uint8_t *bitmap,
size_t bitmap_len) {
ssize_t sent = sendto(flipdot->sockfd, bitmap, bitmap_len, 0,
flipdot->addrs->ai_addr, flipdot->addrs->ai_addrlen);
return (sent == (ssize_t)bitmap_len);
}