diff --git a/.gitignore b/.gitignore index 2ee6a49..10b36d5 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ passwd_file .ccls .clangd .vscode +.cache diff --git a/CMakeLists.txt b/CMakeLists.txt index d89ff82..f1f3554 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,10 @@ add_definitions("-D_DEFAULT_SOURCE") find_package(OpenSSL REQUIRED) set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}) +set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/lib) + +# Add include directories for headers +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/lib) file(GLOB SOURCES src/*.c) file(GLOB TEST src/hashtable.c src/bst.c src/config.c src/list.c src/trie.c @@ -44,8 +48,11 @@ else (APPLE) TARGET_LINK_LIBRARIES(sol pthread ssl crypto crypt) TARGET_LINK_LIBRARIES(sol_test pthread ssl crypto crypt) endif (APPLE) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wunused -Werror -pedantic \ - -Wno-unused-result -std=c11 -O3") + # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wunused -Werror -pedantic \ + # -Wno-unused-result -std=c11 -O3") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wunused -Werror -pedantic \ + -Wno-unused-result -std=c11 -ggdb -fsanitize=address \ + -fsanitize=undefined -fno-omit-frame-pointer -pg") endif (DEBUG) diff --git a/COPYING b/COPYING index ae39990..aae1c79 100644 --- a/COPYING +++ b/COPYING @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2023, Andrea Giacomo Baldan +Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/conf/sol.conf b/conf/sol.conf index a6e907a..cbecfd5 100644 --- a/conf/sol.conf +++ b/conf/sol.conf @@ -6,7 +6,7 @@ # is set, UNIX family socket will be used ip_address 127.0.0.1 -ip_port 8883 +ip_port 1883 # 8883 for MQTT over TLS (MQTT clients such as mosquitto will automatically add TLS) # unix_socket /tmp/sol.sock @@ -30,11 +30,11 @@ tcp_backlog 128 # Interval of time between one stats publish on $SOL topics and the subsequent stats_publish_interval 10s -cafile certs/ca.crt -certfile certs/alaptop.crt -keyfile certs/alaptop.key +# cafile certs/ca.crt +# certfile certs/alaptop.crt +# keyfile certs/alaptop.key # allow_anonymous false # password_file passwd_file -tls_protocols tlsv1,tlsv1_1,tlsv1_2,tlsv1_3 +# tls_protocols tlsv1,tlsv1_1,tlsv1_2,tlsv1_3 diff --git a/include/arena.h b/include/arena.h new file mode 100644 index 0000000..bf6cbf5 --- /dev/null +++ b/include/arena.h @@ -0,0 +1,56 @@ +#ifndef ARENA_H +#define ARENA_H + +#include +#include + +typedef struct pool_allocator { + void *(*alloc)(void *context); + void (*free)(void *context, void *ptr); + void *context; +} Pool_Allocator; + +#define pool_alloc(a) ((a)->alloc((a)->context)) +#define pool_free(a, p) ((a)->free((a)->context, p)) + +void *arena_pool_alloc(void *context); +void arena_pool_free(void *context, void *ptr); + +struct memorypool *memorypool_new(size_t, size_t); +void memorypool_destroy(struct memorypool *); +void *memorypool_alloc(struct memorypool *); +void memorypool_free(struct memorypool *, void *); + +// Free list definition + +typedef struct Free_List_Header { + size_t block_size; + size_t padding; +} Free_List_Header; + +typedef struct Free_List_Node { + struct Free_List_Node *next; + size_t block_size; +} Free_List_Node; + +typedef struct Free_List { + void *data; + size_t size; + size_t used; + Free_List_Node *head; +} Free_List; + +typedef struct arena_allocator { + void *(*alloc)(void *context, size_t size); + void (*free)(void *context, void *ptr); + void *context; +} Arena_Allocator; + +#define arena_alloc(a, s) ((a)->alloc((a)->context, (s))) +#define arena_free(a, p) ((a)->free((a)->context, (p))) + +Free_List *free_list_new(size_t size); +void *free_list_alloc(void *, size_t); +void free_list_free(void *, void *); + +#endif diff --git a/src/bst.h b/include/bst.h similarity index 92% rename from src/bst.h rename to include/bst.h index f56e118..4d73fc9 100644 --- a/src/bst.h +++ b/include/bst.h @@ -1,7 +1,7 @@ /* * BSD 2-Clause License * - * Copyright (c) 2023 Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025 Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -45,6 +45,6 @@ struct bst_node *bst_search(const struct bst_node *, unsigned char); struct bst_node *bst_delete(struct bst_node *, unsigned char); -void bst_destroy(struct bst_node *, void (*free_fn)(struct bst_node *)); +void bst_free(struct bst_node *, void (*free_fn)(struct bst_node *)); #endif diff --git a/src/config.h b/include/config.h similarity index 98% rename from src/config.h rename to include/config.h index b642aee..a911d29 100644 --- a/src/config.h +++ b/include/config.h @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/include/darray.h b/include/darray.h new file mode 100644 index 0000000..22ef7f5 --- /dev/null +++ b/include/darray.h @@ -0,0 +1,26 @@ +#ifndef DARRAY_H +#define DARRAY_H + +#include + +// Dynamic array helpers + +#define da_extend(da) \ + do { \ + (da).capacity *= 2; \ + (da).items = realloc((da).items, (da).capacity * sizeof(*(da).items)); \ + if (!(da).items) { \ + fprintf(stderr, "DA realloc failed"); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +#define da_append(da, item) \ + do { \ + assert((da)); \ + if ((da).length + 1 == (da).capacity) \ + da_extend((da)); \ + (da).items[(da).length++] = (item); \ + } while (0) + +#endif diff --git a/src/ev.h b/include/ev.h similarity index 83% rename from src/ev.h rename to include/ev.h index b9f472e..505f9b3 100644 --- a/src/ev.h +++ b/include/ev.h @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -50,6 +50,8 @@ enum ev_type { struct ev_ctx; +typedef void (*ev_callback)(struct ev_ctx *, void *); + /* * Event struture used as the main carrier of clients informations, it will be * tracked by an array in every context created @@ -57,16 +59,16 @@ struct ev_ctx; struct ev { int fd; int mask; - void *rdata; // opaque pointer for read callback args - void *wdata; // opaque pointer for write callback args - void (*rcallback)(struct ev_ctx *, void *); // read callback - void (*wcallback)(struct ev_ctx *, void *); // write callback + void *rdata; // opaque pointer for read callback args + void *wdata; // opaque pointer for write callback args + ev_callback rcallback; // read callback + ev_callback wcallback; // write callback }; /* * Event loop context, carry the expected number of events to be monitored at * every cycle and an opaque pointer to the backend used as engine - * (Select | Epoll | Kqueue). + * (Select | Epoll | Kqueue).ev * By now we stick with epoll and skip over select, cause as the current * threaded model employed by the server is not very friendly with select * Level-trigger default setting. But it would be quiet easy abstract over the @@ -81,13 +83,13 @@ struct ev_ctx { int stop; int maxevents; unsigned long long fired_events; - struct ev *events_monitored; + struct ev *monitored; void *api; // opaque pointer to platform defined backends }; int ev_init(struct ev_ctx *, int); -void ev_destroy(struct ev_ctx *); +void ev_free(struct ev_ctx *); /* * Poll an event context for events, accepts a timeout or block forever, @@ -113,14 +115,14 @@ void ev_stop(struct ev_ctx *); * ev_fire_event just without an event to be carried. Useful to add simple * descritors like a listening socket o message queue FD. */ -int ev_watch_fd(struct ev_ctx *, int, int); +int ev_watch(struct ev_ctx *, int, int); /* * Remove a FD from the loop, even tho a close syscall is sufficient to remove * the FD from the underlying backend such as EPOLL/SELECT, this call ensure * that any associated events is cleaned out an set to EV_NONE */ -int ev_del_fd(struct ev_ctx *, int); +int ev_delete(struct ev_ctx *, int); /* * Register a new event, semantically it's equal to ev_register_event but @@ -128,17 +130,14 @@ int ev_del_fd(struct ev_ctx *, int); * It could be easily integrated in ev_fire_event call but I prefer maintain * the samantic separation of responsibilities. */ -int ev_register_event(struct ev_ctx *, int, int, - void (*callback)(struct ev_ctx *, void *), void *); +int ev_add_event(struct ev_ctx *, int, int, ev_callback, void *); -int ev_register_cron(struct ev_ctx *, void (*callback)(struct ev_ctx *, void *), - void *, long long, long long); +int ev_add_cron(struct ev_ctx *, ev_callback, void *, long long, long long); /* * Register a new event for the next loop cycle to a FD. Equal to ev_watch_fd * but allow to carry an event object for the next cycle. */ -int ev_fire_event(struct ev_ctx *, int, int, - void (*callback)(struct ev_ctx *, void *), void *); +int ev_oneshot(struct ev_ctx *, int, int, ev_callback, void *); #endif diff --git a/src/handlers.h b/include/handlers.h similarity index 81% rename from src/handlers.h rename to include/handlers.h index 6b28433..fea9b41 100644 --- a/src/handlers.h +++ b/include/handlers.h @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -28,12 +28,13 @@ #ifndef HANDLERS_H #define HANDLERS_H -struct topic; +typedef struct topic Topic; struct mqtt_packet; -struct io_event; +typedef struct connection_context Connection_Context; +typedef struct arena_allocator Arena_Allocator; -int publish_message(struct mqtt_packet *, const struct topic *); +void publish_message(struct mqtt_packet *, const Topic *, Arena_Allocator *); -int handle_command(unsigned, struct io_event *); +int handle_command(Connection_Context *); #endif diff --git a/src/iterator.h b/include/iterator.h similarity index 95% rename from src/iterator.h rename to include/iterator.h index ec656d9..b568f28 100644 --- a/src/iterator.h +++ b/include/iterator.h @@ -1,7 +1,7 @@ /* * BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -47,7 +47,7 @@ struct iterator { struct iterator *iter_new(void *, void (*next)(struct iterator *)); void iter_init(struct iterator *, void *, void (*next)(struct iterator *)); struct iterator *iter_next(struct iterator *); -void iter_destroy(struct iterator *); +void iter_free(struct iterator *); #define FOREACH(it) for (; it && it->ptr; it = iter_next(it)) diff --git a/src/list.h b/include/list.h similarity index 98% rename from src/list.h rename to include/list.h index 6af9b27..6f5b652 100644 --- a/src/list.h +++ b/include/list.h @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan + * Copyright (c) 2025, Andrea Giacomo Baldan * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -56,7 +56,7 @@ List *list_new(int (*destructor)(struct list_node *)); * Release a list, accept a integer flag to control the depth of the free call * (e.g. going to free also data field of every node) */ -void list_destroy(List *, int); +void list_free(List *, int); /* Return list size */ unsigned long list_size(const List *); diff --git a/src/logging.h b/include/logging.h similarity index 97% rename from src/logging.h rename to include/logging.h index fb83e8c..395b46f 100644 --- a/src/logging.h +++ b/include/logging.h @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/memory.h b/include/memory.h similarity index 96% rename from src/memory.h rename to include/memory.h index 68a987d..c0057ff 100644 --- a/src/memory.h +++ b/include/memory.h @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/mqtt.h b/include/mqtt.h similarity index 85% rename from src/mqtt.h rename to include/mqtt.h index 9a7667f..345f6df 100644 --- a/src/mqtt.h +++ b/include/mqtt.h @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -28,7 +28,6 @@ #ifndef MQTT_H #define MQTT_H -#include "ref.h" #include "types.h" // Packing/unpacking error codes @@ -37,14 +36,14 @@ #define MQTT_HEADER_LEN 2 #define MQTT_ACK_LEN 4 -#define MQTT_CLIENT_ID_LEN 64 // including nul char +#define MQTT_CLIENT_ID_LEN 24 // including nul char // Return codes for connect packet #define MQTT_CONNECTION_ACCEPTED 0x00 #define MQTT_UNACCEPTABLE_PROTOCOL_VERSION 0x01 #define MQTT_IDENTIFIER_REJECTED 0x02 #define MQTT_SERVER_UNAVAILABLE 0x03 -#define MQTT_BAD_USERNAME_OR_PASSWORD 0x04 +#define MQTT_BAD_CREDENTIALS 0x04 #define MQTT_NOT_AUTHORIZED 0x05 /* @@ -101,7 +100,7 @@ enum qos_level { AT_MOST_ONCE, AT_LEAST_ONCE, EXACTLY_ONCE }; * | Byte 5 | | * |------------|--------------------------------------------------| */ -union mqtt_header { +typedef union mqtt_header { u8 byte; struct { u8 retain : 1; @@ -109,7 +108,7 @@ union mqtt_header { u8 dup : 1; u8 type : 4; } bits; -}; +} MQTT_Header; /* * MQTT Connect packet, contains a variable header with some connect related @@ -163,7 +162,7 @@ union mqtt_header { * | Byte N+M+K | | * |------------|--------------------------------------------------| */ -struct mqtt_connect { +typedef struct mqtt_connect { union { u8 byte; struct { @@ -184,9 +183,9 @@ struct mqtt_connect { u8 *will_topic; u8 *will_message; } payload; -}; +} MQTT_Connect; -struct mqtt_connack { +typedef struct mqtt_connack { union { u8 byte; struct { @@ -195,70 +194,69 @@ struct mqtt_connack { } bits; }; u8 rc; -}; +} MQTT_Connack; -struct mqtt_subscribe { - u16 pkt_id; +typedef struct mqtt_subscribe { + u16 id; u16 tuples_len; struct { u8 qos; u16 topic_len; u8 *topic; } *tuples; -}; +} MQTT_Subscribe; -struct mqtt_unsubscribe { - u16 pkt_id; +typedef struct mqtt_unsubscribe { + u16 id; u16 tuples_len; struct { u16 topic_len; u8 *topic; } *tuples; -}; +} MQTT_Unsubscribe; -struct mqtt_suback { - u16 pkt_id; +typedef struct mqtt_suback { + u16 id; u16 rcslen; u8 *rcs; -}; +} MQTT_Suback; -struct mqtt_publish { - u16 pkt_id; +typedef struct mqtt_publish { + u16 id; u16 topiclen; u8 *topic; u32 payloadlen; u8 *payload; -}; +} MQTT_Publish; -struct mqtt_ack { - u16 pkt_id; -}; +typedef struct mqtt_ack { + u16 id; +} MQTT_Ack; typedef struct mqtt_ack mqtt_puback; typedef struct mqtt_ack mqtt_pubrec; typedef struct mqtt_ack mqtt_pubrel; typedef struct mqtt_ack mqtt_pubcomp; typedef struct mqtt_ack mqtt_unsuback; -typedef union mqtt_header mqtt_pingreq; -typedef union mqtt_header mqtt_pingresp; -typedef union mqtt_header mqtt_disconnect; +typedef MQTT_Header mqtt_pingreq; +typedef MQTT_Header mqtt_pingresp; +typedef MQTT_Header mqtt_disconnect; -struct mqtt_packet { - union mqtt_header header; +typedef struct mqtt_packet { + MQTT_Header header; union { // This will cover PUBACK, PUBREC, PUBREL, PUBCOMP and UNSUBACK - struct mqtt_ack ack; + MQTT_Ack ack; // This will cover PINGREQ, PINGRESP and DISCONNECT mqtt_pingreq pingreq; - struct mqtt_connect connect; - struct mqtt_connack connack; - struct mqtt_suback suback; - struct mqtt_publish publish; - struct mqtt_subscribe subscribe; - struct mqtt_unsubscribe unsubscribe; + MQTT_Connect connect; + MQTT_Connack connack; + MQTT_Suback suback; + MQTT_Publish publish; + MQTT_Subscribe subscribe; + MQTT_Unsubscribe unsubscribe; }; - struct ref refcount; -}; +} MQTT_Packet; /* * Encoding packet length function, follows the OASIS specs, encode the total @@ -270,25 +268,26 @@ struct mqtt_packet { * remaining length or not. * Returns the number of bytes used to store the value passed. */ -int mqtt_encode_length(u8 *, usize); +int mqtt_write_length(u8 *, usize); /* * The reverse of the encoding function, returns the value of the size decoded */ -usize mqtt_decode_length(u8 *, unsigned *); +usize mqtt_read_length(u8 *, unsigned *); /* * Pack to binary an MQTT packet, internally it uses a dispatch table to call * the right pack function based on the packet opcode. */ -int mqtt_unpack(u8 *, struct mqtt_packet *, u8, usize); +typedef struct arena_allocator Arena_Allocator; +int mqtt_read(u8 *, MQTT_Packet *, u8, usize, Arena_Allocator *allocator); /* * Unpack from binary to an mqtt_packet structure. Internally it uses a * dispatch table to call the right unpack function based on the opcode * expected to read. */ -usize mqtt_pack(const struct mqtt_packet *, u8 *); +usize mqtt_write(const MQTT_Packet *, u8 *); /* * MQTT Build helpers @@ -296,39 +295,41 @@ usize mqtt_pack(const struct mqtt_packet *, u8 *); * They receive a pointer to a struct mqtt_packet and additional informations * to be stored inside. Just plain builder functions. */ -void mqtt_ack(struct mqtt_packet *, u16); +void mqtt_ack(MQTT_Packet *, u16); -void mqtt_connack(struct mqtt_packet *, u8, u8); +void mqtt_connack(MQTT_Packet *, u8, u8); -void mqtt_suback(struct mqtt_packet *, u16, u8 *, u16); +void mqtt_suback(MQTT_Packet *, u16, u8 *, u16); -void mqtt_publish(struct mqtt_packet *, u16, usize, u8 *, usize, u8 *); +void mqtt_publish(MQTT_Packet *, u16, usize, u8 *, usize, u8 *); /* * Release the memory allocated through helpers function calls based on the * opcode of the MQTT packet passed */ -void mqtt_packet_destroy(struct mqtt_packet *); +void mqtt_packet_free(MQTT_Packet *); -void mqtt_set_dup(struct mqtt_packet *); +void mqtt_set_dup(MQTT_Packet *); /* * Helper function used to pack ACK packets, mono as the single field `packet * identifier` that ACKs packet carries */ -int mqtt_pack_mono(u8 *, u8, u16); +int mqtt_write_ack(u8 *, u8, u16); /* * Returns the size of a packet. Useful to pack functions to know the expected * buffer size of the packet based on the opcode. Accept an optional pointer * to get the len reserved for storing the remaining length of the full packet */ -usize mqtt_size(const struct mqtt_packet *, usize *); +usize mqtt_size(const MQTT_Packet *, usize *); + +typedef struct pool_allocator Pool_Allocator; /* * Allocate struct mqtt_packet on the heap. This should be used in place of * malloc/calloc in order to leverage the refcounter */ -struct mqtt_packet *mqtt_packet_alloc(u8); +MQTT_Packet *mqtt_packet_alloc(u8, Pool_Allocator *); #endif diff --git a/src/network.h b/include/network.h similarity index 98% rename from src/network.h rename to include/network.h index 3fb834d..bcfc27f 100644 --- a/src/network.h +++ b/include/network.h @@ -1,7 +1,7 @@ /* * BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/pack.h b/include/pack.h similarity index 82% rename from src/pack.h rename to include/pack.h index 52d844b..df92057 100644 --- a/src/pack.h +++ b/include/pack.h @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,35 +33,31 @@ /* Reading data on const u8 pointer */ // bytes -> int16_t -i16 unpacki16(u8 *); +i16 read_i16(const u8 *); // bytes -> uint16_t -u16 unpacku16(u8 *); +u16 read_u16(const u8 *); // bytes -> int32_t -i32 unpacki32(u8 *); +i32 read_i32(const u8 *); // bytes -> uint32_t -u32 unpacku32(u8 *); +u32 read_u32(const u8 *); // bytes -> int64_t -i64 unpacki64(u8 *); +i64 read_i64(const u8 *); // bytes -> uint64_t -u64 unpacku64(u8 *); - -/* Write data on const u8 pointer */ -// append a u8 -> bytes into the bytestring -void pack_u8(u8 **, u8); +u64 read_u64(const u8 *); // append a uint16_t -> bytes into the bytestring -void packi16(u8 *, u16); +void write_i16(u8 *, u16); // append a int32_t -> bytes into the bytestring -void packi32(u8 *, u32); +void write_i32(u8 *, u32); // append a uint64_t -> bytes into the bytestring -void packi64(u8 *, u64); +void write_i64(u8 *, u64); /* * pack() -- store data dictated by the format string in the buffer @@ -76,7 +72,7 @@ void packi64(u8 *, u64); * * (16-bit unsigned length is automatically prepended to strings) */ -usize pack(u8 *, char *, ...); +usize write_struct(u8 *, char *, ...); /* * unpack() -- unpack data dictated by the format string into the buffer @@ -92,12 +88,14 @@ usize pack(u8 *, char *, ...); * (string is extracted based on its stored length, but 's' can be * prepended with a max length) */ -usize unpack(u8 *, char *, ...); +usize read_struct(u8 *, char *, ...); + +i64 read_int(u8 **, i8); -i64 unpack_integer(u8 **, i8); +typedef struct arena_allocator Arena_Allocator; -u8 *unpack_bytes(u8 **, usize); +u8 *read_bytes(u8 **, usize, Arena_Allocator *); -u16 unpack_string16(u8 **, u8 **); +u16 read_string_u16(u8 **, u8 **, Arena_Allocator *); #endif diff --git a/src/ref.h b/include/ref.h similarity index 94% rename from src/ref.h rename to include/ref.h index 9b0b4c5..4dca5ef 100644 --- a/src/ref.h +++ b/include/ref.h @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -35,11 +35,9 @@ #pragma once -#include - struct ref { void (*free)(const struct ref *); - volatile atomic_int count; + int count; }; static inline void ref_inc(const struct ref *ref) diff --git a/src/server.h b/include/server.h similarity index 68% rename from src/server.h rename to include/server.h index b1aceb5..6af5d6b 100644 --- a/src/server.h +++ b/include/server.h @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -28,16 +28,10 @@ #ifndef SERVER_H #define SERVER_H +#include "arena.h" #include "mqtt.h" #include -/* - * Number of worker threads to be created. Each one will host his own ev_ctx - * loop. This doesn't take into account the main thread, so to know the total - * number of running loops +1 must be added to the THREADSNR value. - */ -#define THREADSNR 2 - /* * Epoll default settings for concurrent events monitored and timeout, -1 * means no timeout at all, blocking undefinitely @@ -48,50 +42,40 @@ /* Initial memory allocation for clients on server start-up, it should be * equal to ~40 MB, read and write buffers are initialized lazily */ -#define BASE_CLIENTS_NUM 1024 * 128 +#define BASE_CLIENTS_NUM 256 -/* - * IO event strucuture, it's the main information that will be communicated - * between threads, every request packet will be wrapped into an IO event and - * passed to the work EPOLL, in order to be handled by the worker thread pool. - * Then finally, after the execution of the command, it will be updated and - * passed back to the IO epoll loop to be written back to the requesting client - */ -struct io_event { - struct client *client; - struct mqtt_packet data; -}; +typedef struct connection_context Connection_Context; /* Global informations statistics structure */ struct sol_info { /* Number of clients currently connected */ - atomic_size_t active_connections; + size_t active_connections; /* Total number of clients connected since the start */ - atomic_size_t total_connections; + size_t total_connections; /* Total number of sent messages */ - atomic_size_t messages_sent; + size_t messages_sent; /* Total number of received messages */ - atomic_size_t messages_recv; + size_t messages_recv; /* Timestamp of the start time */ - atomic_size_t start_time; + size_t start_time; /* Seconds passed since the start */ - atomic_size_t uptime; + size_t uptime; /* Total number of bytes received */ - atomic_size_t bytes_sent; + size_t bytes_sent; /* Total number of bytes sent out */ - atomic_size_t bytes_recv; + size_t bytes_recv; }; #define INIT_INFO \ do { \ - info.active_connections = ATOMIC_VAR_INIT(0); \ - info.total_connections = ATOMIC_VAR_INIT(0); \ - info.messages_sent = ATOMIC_VAR_INIT(0); \ - info.messages_recv = ATOMIC_VAR_INIT(0); \ - info.start_time = ATOMIC_VAR_INIT(0); \ - info.uptime = ATOMIC_VAR_INIT(0); \ - info.bytes_sent = ATOMIC_VAR_INIT(0); \ - info.bytes_recv = ATOMIC_VAR_INIT(0); \ + info.active_connections = 0; \ + info.total_connections = 0; \ + info.messages_sent = 0; \ + info.messages_recv = 0; \ + info.start_time = 0; \ + info.uptime = 0; \ + info.bytes_sent = 0; \ + info.bytes_recv = 0; \ } while (0) /* @@ -107,17 +91,25 @@ extern struct sol_info info; * pending_msgs and pendings_acks are two arrays used to track remaining * messages to push out and acks respectively. */ +typedef struct session Session; +typedef struct topic_repo Topic_Repo; struct server { // The main topics store - struct topic_store *store; + Topic_Repo *repo; // A memory pool for clients allocation - struct memorypool *pool; + Arena_Allocator allocator; + // A memory pool for sessions + Arena_Allocator session_allocator; + // MQTT packets' payload allocator + Arena_Allocator mqtt_allocator; + // MQTT packets allocator + Pool_Allocator packet_allocator; // Our clients map, it's a handle pointer for UTHASH APIs, must be set to // NULL - struct client *clients_map; + Connection_Context *contexts; // The global session map, another UTHASH handle pointer, must be set to // NULL - struct client_session *sessions; + Session *sessions; // UTHASH handle pointer for authentications struct authentication *auths; // Application TLS context @@ -139,7 +131,7 @@ int start_server(const char *, const char *); * schedules an EV_WRITE event with a client pointer set to write carried * contents out on the socket descriptor. */ -void enqueue_event_write(const struct client *); +void enqueue_event_write(const Connection_Context *); /* * Make the entire process a daemon running in background diff --git a/src/sol_internal.h b/include/sol_internal.h similarity index 50% rename from src/sol_internal.h rename to include/sol_internal.h index 7d64131..1a721e6 100644 --- a/src/sol_internal.h +++ b/include/sol_internal.h @@ -1,7 +1,7 @@ /* * BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,16 +26,16 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include "arena.h" #include "list.h" #include "mqtt.h" #include "network.h" -#include "pack.h" #include "trie.h" #include "uthash.h" -#include "util.h" -#include #include +#define BUFSIZE 1024 + /* Generic return codes without a defined purpose */ #define SOL_OK 0 #define SOL_ERR 1 @@ -66,62 +66,61 @@ #define NOREPLY 1 /* The maximum number of pending/not acknowledged packets for each client */ -#define MAX_INFLIGHT_MSGS 65536 +#define MAX_INFLIGHT_MSGS 4096 + +/* + * An MQTT subscriber wraps a client session and is composed by a granted QoS + * which is the QoS given by the server for each topic it's subscribed, an ID + * which is the same of the client it refers to and two utility members to + * handle it's sharing between structures. + * + * It's hashable according to UTHASH APIs. For more info check + * https://troydhanson.github.io/uthash/userguide.html + */ +typedef struct session Session; +typedef struct subscriber { + Session *session; // Session referring to a client + char cid[MQTT_CLIENT_ID_LEN]; + u8 granted_qos; // The QoS given by the server for each topic + UT_hash_handle hh; // UTHASH handle, needed to use UTHASH macros +} Subscriber; + +/* + * Utility struct to store wildcard subscriptions. Just wrap a subscriber + * paired with a topic name and a flag to indicate if it's a '#' multilevel + * subscription or not. + */ +typedef struct subscription { + bool multilevel; // Flag for '#' subscriptions + const char *topic; // Topic name the subscription refers to + Subscriber *subscriber; // Reference to the subscriber +} Subscription; /* * An MQTT topic is composed by a name which identify it, a retained message * which must be forwarded to all subscribing clients and a map of subscribers, - * the handle is a struct subscriber pointer which have to be initialized at + * the handle is a Subscriber pointer which have to be initialized at * NULL. * * See https://troydhanson.github.io/uthash/userguide.html for more info */ -struct topic { +typedef struct topic { const char *name; - unsigned char *retained_msg; - struct subscriber *subscribers; /* UTHASH handle pointer, must be NULL */ -}; + u8 *retained_msg; + Subscriber *subscribers; /* UTHASH handle pointer, must be NULL */ +} Topic; /* - * Topic store keep track of all topics and wildcards registered, using a + * Topic repo keep track of all topics and wildcards registered, using a * trie as underlying data structure */ -struct topic_store { +typedef struct topic_repo { // The main topics Trie structure Trie *topics; // A list of wildcards subscriptions, as it's not possible to know in // advance what topics will match some wildcard subscriptions List *wildcards; -}; - -/* - * An MQTT subscriber wraps a client session and is composed by a granted QoS - * which is the QoS given by the server for each topic it's subscribed, an ID - * which is the same of the client it refers to and two utility members to - * handle it's sharing between structures. - * - * It's hashable according to UTHASH APIs. For more info check - * https://troydhanson.github.io/uthash/userguide.html - */ -struct subscriber { - struct client_session *session; /* Session referring to a client */ - unsigned char granted_qos; /* The QoS given by the server for each topic */ - char id[MQTT_CLIENT_ID_LEN]; /* Client ID key */ - UT_hash_handle hh; /* UTHASH handle, needed to use UTHASH macros */ - struct ref - refcount; /* Reference counting struct, to share the struct easily */ -}; - -/* - * Utility struct to store wildcard subscriptions. Just wrap a subscriber - * paired with a topic name and a flag to indicate if it's a '#' multilevel - * subscription or not. - */ -struct subscription { - bool multilevel; /* Flag for '#' subscriptions */ - const char *topic; /* Topic name the subscription refers to */ - struct subscriber *subscriber; /* Reference to the subscriber */ -}; +} Topic_Repo; /* * Pending messages remaining to be sent out, they can be either PUBLISH or @@ -130,125 +129,84 @@ struct subscription { * and the packet himself. * It's meant to be used in a fixed length array. */ -struct inflight_msg { - time_t seen; /* Timestamp of the last time we have seen this msg */ - struct mqtt_packet - *packet; /* The payload to be written out in case of timeout */ - unsigned char qos; /* The QoS at the time of the publish */ -}; +typedef struct inflight_message { + time_t lastack_at; // Timestamp of the last time we have seen this msg + MQTT_Packet *packet; // The payload to be written out in case of timeout +} Inflight_Message; /* - * The client actions can be summarized as a roughly simple state machine, - * comprised by 4 states: - * - WAITING_HEADER it's the base state, waiting for the next packet to be - * received - * - WAITING_LENGTH the second state, a packet has arrived but it's not - * complete yet. Accorting to MQTT protocol, after the first - * byte we need to wait 1 to 4 more bytes based on the - * encoded length (use continuation bit to state the number - * of bytes needed, see http://docs.oasis-open.org/mqtt/mqtt/ - * v3.1.1/os/mqtt-v3.1.1-os.html for more info) - * - WAITING_DATA it's the step required to receive the full byte stream as - * the encoded length describe. We wait for the effective - * payload in this state. - * - SENDING_DATA the last status, a complete packet has been received and - * has to be processed and reply back if needed. + * Every client has a session which track his subscriptions, possible missed + * messages during disconnection time (that iff clean_session is set to false), + * inflight messages and the message ID for each one. + * A maximum of 4096 mid can be used at the same time according to MQTT specs, + * so i_msgs, thus being allocated on the heap during the init, will be + * of 4096 length. + * + * It's a hashable struct that will be tracked during the entire lifetime of + * the application, governed by the clean_session flag on connection from + * clients */ -enum client_status { - WAITING_HEADER, - WAITING_LENGTH, - WAITING_DATA, - SENDING_DATA -}; +typedef struct session { + List * + subscriptions; // All the clients subscriptions, stored as topic structs + char cid[MQTT_CLIENT_ID_LEN]; // The client_id the session refers to + u16 next_mid; // The next 'free' message ID + u16 inflights; // Just a counter stating the presence of inflight messages + bool clean_session; // Clean session flag in case of timeout + MQTT_Packet *lwt; // A possibly NULL LWT message, will be set on connection + Inflight_Message + i_msgs[MAX_INFLIGHT_MSGS]; // Inflight MSGs that must be sent out DUP + UT_hash_handle hh; // UTHASH handle, needed to use UTHASH macros +} Session; + +/* + * The connection context states can be summarized as a roughly simple state + * machine, comprised by 3 states: + * - CS_OPEN it's the base state, waiting for the next packet to be + * received, indicates an active connection + * - CS_CLOSING the second state, a connection has been closed on the sending + * end, the context must be cleaned up and deactivated + * - CS_CLOSE the last state, represents an inactive connection + */ +typedef enum connection_state { + CS_OPEN, + CS_CLOSING, + CS_CLOSED +} Connection_State; /* * Wrapper structure around a connected client, each client can be a publisher * or a subscriber, it can be used to track sessions too. - * As of now, no allocations will be fired, jsut a big pool of memory at the + * As of now, no allocations will be fired, just a big pool of memory at the * start of the application will serve us a client pool, read and write buffers * are initialized lazily. * * It's an hashable struct which will be tracked during the execution of the * application, see https://troydhanson.github.io/uthash/userguide.html. */ -struct client { - struct ev_ctx - *ctx; /* An event context refrence mostly used to fire write events */ - int rc; /* Return code of the message just handled */ - int status; /* Current status of the client (state machine) */ - volatile atomic_int rpos; /* The nr of bytes to skip after a complete - * packet has * been read. This because according - * to MQTT, length is encoded on multiple bytes - * according to it's size, using continuation bit - * as a technique to encode it. We don't want to - * decode the length two times when we already - * know it, so we need an offset to know where - * the actual packet will start - */ - volatile atomic_size_t read; /* The number of bytes already read */ - volatile atomic_size_t - toread; /* The number of bytes that have to be read */ - unsigned char *rbuf; /* The reading buffer */ - volatile atomic_size_t wrote; /* The number of bytes already written */ - volatile atomic_size_t towrite; /* The number of bytes we have to write */ - unsigned char *wbuf; /* The writing buffer */ - char client_id[MQTT_CLIENT_ID_LEN]; /* The client ID according to MQTT specs - */ +typedef struct connection_context { + struct ev_ctx *ctx; /* An event context refrence to handle callbacks */ + // Pool_Allocator allocator; + Arena_Allocator allocator; + Connection_State state; /* Current state of the client (state machine) */ + usize read; /* The number of bytes already read */ + usize written; /* The number of bytes already written */ + usize write_total; /* The number of bytes we have to write */ + u8 recv_buf[BUFSIZE]; + u8 send_buf[BUFSIZE * 8]; + char cid[MQTT_CLIENT_ID_LEN]; /* The client ID according to MQTT specs */ + MQTT_Packet data; struct connection conn; /* A connection structure, takes care of plain or * TLS encrypted communication by using callbacks */ - struct client_session *session; /* The session associated to the client */ - time_t last_seen; /* The timestamp of the last action performed */ - bool online; /* Just an online flag */ - bool connected; /* States if the client has already processed a connection - packet */ - bool has_lwt; /* States if the connection packet carried a LWT message */ - bool clean_session; /* States if the connection packet was set to clean - session */ - pthread_mutex_t mutex; /* Inner lock for the client, this avoid - race-conditions on shared parts */ - UT_hash_handle hh; /* UTHASH handle, needed to use UTHASH macros */ -}; - -/* - * Every client has a session which track his subscriptions, possible missed - * messages during disconnection time (that iff clean_session is set to false), - * inflight messages and the message ID for each one. - * A maximum of 65535 mid can be used at the same time according to MQTT specs, - * so i_acks, i_msgs, thus being allocated on the heap during the init, will be - * of 65535 length each. - * - * It's a hashable struct that will be tracked during the entire lifetime of - * the application, governed by the clean_session flag on connection from - * clients - */ -struct client_session { - unsigned next_free_mid; /* The next 'free' message ID */ - List *subscriptions; /* All the clients subscriptions, stored as topic - structs */ - List *outgoing_msgs; /* Outgoing messages during disconnection time, stored - as mqtt_packet pointers */ - volatile atomic_ushort inflights; /* Just a counter stating the presence of - inflight messages */ - bool clean_session; /* Clean session flag */ - char session_id[MQTT_CLIENT_ID_LEN]; /* The client_id the session refers to - */ - struct mqtt_packet - lwt_msg; /* A possibly NULL LWT message, will be set on connection */ - time_t *i_acks; /* Inflight ACKs that must be cleared */ - struct inflight_msg * - i_msgs; /* Inflight MSGs that must be sent out DUP in case of timeout */ - UT_hash_handle hh; /* UTHASH handle, needed to use UTHASH macros */ - struct ref - refcount; /* Reference counting struct, to share the struct easily */ -}; - -/* - * Simple mutex for contexted critical areas, mainly used in the handlers - * module, in server the only useful use are when creating and deactivating - * clients - */ -extern pthread_mutex_t mutex; + Session *session; /* The session associated to the client */ + bool online; /* Just an online flag */ + bool connected; /* States if the client has already processed a connection + packet */ + bool clean_session; /* States if the connection packet was set to clean + session */ + UT_hash_handle hh; /* UTHASH handle, needed to use UTHASH macros */ +} Connection_Context; struct server; @@ -256,7 +214,7 @@ struct server; * Checks if a client is subscribed to a topic by trying to fetch the * client_session by its ID on the subscribers inner hashmap of the topic. */ -bool is_subscribed(const struct topic *, const struct client_session *); +bool is_subscribed(const Topic *, const Session *); /* * Allocate memory on the heap to create and return a pointer to a struct @@ -264,7 +222,7 @@ bool is_subscribed(const struct topic *, const struct client_session *); * instantiating a reference counter to 0. * It may fail as it needs to allocate some bytes on the heap. */ -struct subscriber *subscriber_new(struct client_session *, unsigned char); +Subscriber *subscriber_new(Session *, u8); /* * Allocate memory on the heap to clone a subscriber pointer, deep copies all @@ -273,7 +231,7 @@ struct subscriber *subscriber_new(struct client_session *, unsigned char); * allocated pointer is returned. * It may fail as it needs to allocate some bytes on the heap. */ -struct subscriber *subscriber_clone(const struct subscriber *); +Subscriber *subscriber_clone(const Subscriber *); /* * Initialize a struct topic pointer by setting its name, subscribers and @@ -281,19 +239,19 @@ struct subscriber *subscriber_clone(const struct subscriber *); * The function expects a non-null pointer and can't fail, if a null topic * is passed, the function return prematurely. */ -void topic_init(struct topic *, const char *); +void topic_init(Topic *, const char *); /* * Allocate a new topic struct on the heap, initialize it then return a pointer * to it. The function can fail as a memory allocation is requested, if it * fails the program execution graceful crash. */ -struct topic *topic_new(const char *); +Topic *topic_new(const char *); /* * Deallocate the topic name, retained_msg and all its subscribers */ -void topic_destroy(struct topic *); +void topic_free(Topic *); /* * Allocate a new subscriber struct on the heap referring to the passed in @@ -301,18 +259,17 @@ void topic_destroy(struct topic *); * The function can fail as a memory allocation is requested, if it fails the * program execution graceful crash. */ -struct subscriber *topic_add_subscriber(struct topic *, struct client_session *, - unsigned char); +Subscriber *topic_add_subscriber(Topic *, Session *, u8); /* * Remove a subscriber from the topic, the subscriber to be removed refers to * the client_id belonging to the client pointer passed in. * The subscriber deletion is really a reference count subtraction, DECREF * macro takes care of the counter, if it reaches 0 it de-allocates the memory - * reserved to the struct subscriber. + * reserved to the Subscriber. * The function can't fail. */ -void topic_del_subscriber(struct topic *, struct client *); +void topic_del_subscriber(Topic *, struct connection_context *); /* * Allocate a new store structure on the heap and return it after its @@ -320,19 +277,19 @@ void topic_del_subscriber(struct topic *, struct client *); * wildcard topics. * The function may gracefully crash as the memory allocation may fail. */ -struct topic_store *topic_store_new(void); +Topic_Repo *topic_repo_new(void); /* * Deallocate heap memory for the list and every wildcard item stored into, * also the store is deallocated */ -void topic_store_destroy(struct topic_store *); +void topic_repo_free(Topic_Repo *); /* * Return a topic associated to a topic name from the store, returns NULL if no * topic is found. */ -struct topic *topic_store_get(const struct topic_store *, const char *); +Topic *topic_repo_fetch(const Topic_Repo *, const char *); /* * Return a topic associated to a topic name from the store, if no topic is @@ -341,49 +298,47 @@ struct topic *topic_store_get(const struct topic_store *, const char *); * The function may fail as in case of no topic found it tries to allocate * space on the heap for the new inserted topic. */ -struct topic *topic_store_get_or_put(struct topic_store *, const char *); +Topic *topic_repo_fetch_default(Topic_Repo *, const char *); /* * Check if the store contains a topic by name key */ -bool topic_store_contains(const struct topic_store *, const char *); +bool topic_repo_contains(const Topic_Repo *, const char *); /* * Insert a topic into the store or update it if already present */ -void topic_store_put(struct topic_store *, struct topic *); +void topic_repo_put(Topic_Repo *, Topic *); /* * Remove a topic into the store */ -void topic_store_del(struct topic_store *, const char *); +void topic_repo_delete(Topic_Repo *, const char *); /* * Add a wildcard topic to the topic_store struct, does not check if it already * exists */ -void topic_store_add_wildcard(struct topic_store *, struct subscription *); +void topic_repo_add_wildcard(Topic_Repo *, Subscription *); /* * Remove a wildcard by id key from the topic_store struct */ -void topic_store_remove_wildcard(struct topic_store *, char *); +void topic_repo_remove_wildcard(Topic_Repo *, char *); /* * Run a function to each node of the topic_store trie holding the topic * entries */ -void topic_store_map(struct topic_store *, const char *, - void (*fn)(struct trie_node *, void *), void *); +void topic_repo_map(Topic_Repo *, const char *, + void (*fn)(struct trie_node *, void *), void *); /* * Check if the wildcards list of the topic_store is empty */ -bool topic_store_wildcards_empty(const struct topic_store *); +bool topic_repo_wildcards_empty(const Topic_Repo *); -#define topic_store_wildcards_foreach(item, store) \ +#define topic_repo_wildcards_foreach(item, store) \ list_foreach(item, store->wildcards) -#define has_inflight(session) ((session)->inflights > 0) - -#define inflight_msg_clear(msg) DECREF((msg)->packet, struct mqtt_packet) +#define has_inflight(session) ((session)->inflights > 0) diff --git a/src/trie.h b/include/trie.h similarity index 96% rename from src/trie.h rename to include/trie.h index 1f2ea43..0df45a7 100644 --- a/src/trie.h +++ b/include/trie.h @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan + * Copyright (c) 2025, Andrea Giacomo Baldan * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -102,9 +102,9 @@ bool trie_delete(Trie *, const char *); */ bool trie_find(const Trie *, const char *, void **); -void trie_node_destroy(struct trie_node *, size_t *, trie_destructor *); +void trie_node_free(struct trie_node *, size_t *, trie_destructor *); -void trie_destroy(Trie *); +void trie_free(Trie *); /* * Remove all keys matching a given prefix in a less than linear time diff --git a/src/types.h b/include/types.h similarity index 96% rename from src/types.h rename to include/types.h index 7352548..98bfd1c 100644 --- a/src/types.h +++ b/include/types.h @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/util.h b/include/util.h similarity index 96% rename from src/util.h rename to include/util.h index 0a4ff44..b2aa04a 100644 --- a/src/util.h +++ b/include/util.h @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/uthash.h b/lib/uthash.h similarity index 99% rename from src/uthash.h rename to lib/uthash.h index 2c42e9a..c53a90c 100644 --- a/src/uthash.h +++ b/lib/uthash.h @@ -145,13 +145,14 @@ typedef unsigned char uint8_t; #endif /* initial number of buckets */ -#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ -#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets \ - */ -#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 \ + 5U /* lg2 of initial number of buckets \ + */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ /* calculate the element whose hash handle address is hhp */ -#define ELMT_FROM_HH(tbl, hhp) ((void *)(((char *)(hhp)) - ((tbl)->hho))) +#define ELMT_FROM_HH(tbl, hhp) ((void *)(((char *)(hhp)) - ((tbl)->hho))) /* calculate the hash handle from element address elp */ #define HH_FROM_ELMT(tbl, elp) \ ((UT_hash_handle *)(void *)(((char *)(elp)) + ((tbl)->hho))) diff --git a/src/arena.c b/src/arena.c new file mode 100644 index 0000000..c062f47 --- /dev/null +++ b/src/arena.c @@ -0,0 +1,366 @@ +#include "arena.h" +#include "memory.h" +#include +#include + +#define DEFAULT_ALIGNMENT sizeof(void *) +#define is_power_of_two(x) ((x != 0) && ((x & (x - 1)) == 0)) + +// +// MEMORY POOL BACKEND +// + +/* + * Simple memory object-pool, the purpose is to allow for fixed size objects to + * be pre-allocated and re-use of memory blocks, so no size have to be + * specified like in a normal malloc but only alloc and free of a pointer is + * possible. + */ +struct memorypool { + void *memory; + void *free; + struct memorypool *next; + int block_used; + size_t blocks_nr; + size_t blocksize; +}; + +// static void memorypool_resize(struct memorypool *); + +static void memorypool_add_pool(struct memorypool **pool); + +struct memorypool *memorypool_new(size_t blocks_nr, size_t blocksize) +{ + struct memorypool *pool = try_alloc(sizeof(*pool)); + blocksize = blocksize >= sizeof(intptr_t) ? blocksize : sizeof(intptr_t); + pool->memory = try_calloc(blocks_nr, blocksize); + pool->free = pool->memory; + pool->blocks_nr = blocks_nr; + pool->blocksize = blocksize; + if (!pool->free) { + free_memory(pool); + return NULL; + } + /* + * We pre-assign the position of each free block in the free pointer, this + * way we know every block position before allocating new memory, we'll + * call it the header of each block, where we store the offset in memory + * to reach the next free slot: + * + * ____________ + * _| 0x1ad45f02 | (0+blocksize) + * | |------------| + * | | . | + * | | . | + * |_|------------| + * _| 0x2ff43da1 | (1+blocksize) + * | |------------| + * | | . | + * | | . | + * |_|------------| + * | 0x98fff34a | (n+blocksize) + * |------------| + * | . | + * + * Just before assigning a free block of memory, we update the free pointer, + * pointing it to the memory address previously stored as r-value in it. + * This way everytime we allocate a new block we can refresh the next free + * block in the list. + */ + intptr_t *ptr = pool->free; + for (size_t i = 1; i != blocks_nr; ++i) { + *ptr = (intptr_t)i; + ptr = (intptr_t *)((char *)ptr + blocksize); + } + pool->block_used = 0; + return pool; +} + +void memorypool_destroy(struct memorypool *pool) +{ + free_memory(pool->memory); + free_memory(pool); +} + +void *memorypool_alloc(struct memorypool *pool) +{ + if (pool->block_used == pool->blocks_nr - 1) + memorypool_add_pool(&pool); + void *ptr = pool->free; + /* + * After pointing the return pointer to the next free block, we need to + * update the next free block address on the free pointer. The address is + * already stored in the "header" of the block. + */ + pool->free = (intptr_t *)((char *)pool->memory + + (*((intptr_t *)pool->free)) * pool->blocksize); + pool->block_used++; + return memset(ptr, 0x00, pool->blocksize); +} + +void memorypool_free(struct memorypool *pool, void *ptr) +{ + /* + * Here we just need to point the header of the pointer to the next free + * location and udpate the current free location by pointing it to the + * free'd pointer + */ + *((intptr_t *)ptr) = *((intptr_t *)pool->free); + if (pool->block_used == pool->blocks_nr - 1) + memorypool_add_pool(&pool); + pool->free = ptr; + pool->block_used--; +} + +// When pool is full, allocate a new pool and link it +static void memorypool_add_pool(struct memorypool **pool) +{ + struct memorypool *new_pool = + memorypool_new((*pool)->blocks_nr, (*pool)->blocksize); + new_pool->next = *pool; + *pool = new_pool; +} + +void *arena_pool_alloc(void *context) { return memorypool_alloc(context); } + +void arena_pool_free(void *context, void *ptr) +{ + memorypool_free(context, ptr); +} + +// +// Free-List implementation Odin's like +// + +static size_t calc_padding_with_header(uintptr_t ptr, uintptr_t alignment, + size_t header_size); + +static void free_list_free_all(Free_List *fl) +{ + fl->used = 0; + Free_List_Node *first_node = (Free_List_Node *)fl->data; + first_node->block_size = fl->size; + first_node->next = NULL; + fl->head = first_node; +} + +static void free_list_init(Free_List *fl, void *data, size_t size) +{ + fl->data = data; + fl->size = size; + free_list_free_all(fl); +} + +Free_List *free_list_new(size_t size) +{ + Free_List *fl = try_calloc(1, sizeof(*fl)); + void *chunk = try_calloc(1, size); + + // uintptr_t raw = (uintptr_t)malloc(size + 16 - 1); + // uintptr_t aligned = (raw + 16 - 1) & ~(16 - 1); + free_list_init(fl, chunk, size); + return fl; +} + +static void free_list_node_insert(Free_List_Node **phead, + Free_List_Node *prev_node, + Free_List_Node *new_node) +{ + if (prev_node == NULL) { + if (*phead != NULL) { + new_node->next = *phead; + } else { + *phead = new_node; + } + } else { + if (prev_node->next == NULL) { + prev_node->next = new_node; + new_node->next = NULL; + } else { + new_node->next = prev_node->next; + prev_node->next = new_node; + } + } +} + +static void free_list_node_remove(Free_List_Node **phead, + Free_List_Node *prev_node, + Free_List_Node *del_node) +{ + if (prev_node == NULL) { + *phead = del_node->next; + } else { + prev_node->next = del_node->next; + } +} + +static size_t calc_padding_with_header(uintptr_t ptr, uintptr_t alignment, + size_t header_size) +{ + uintptr_t p, a, modulo, padding, needed_space; + + assert(is_power_of_two(alignment)); + + p = ptr; + a = alignment; + modulo = p & (a - 1); // (p % a) as it assumes alignment is a power of two + + padding = 0; + needed_space = 0; + + if (modulo != 0) { // Same logic as 'align_forward' + padding = a - modulo; + } + + needed_space = (uintptr_t)header_size; + + if (padding < needed_space) { + needed_space -= padding; + + if ((needed_space & (a - 1)) != 0) { + padding += a * (1 + (needed_space / a)); + } else { + padding += a * (needed_space / a); + } + } + + return (size_t)padding; +} + +static Free_List_Node *free_list_find_first(Free_List *fl, size_t size, + size_t alignment, size_t *padding_, + Free_List_Node **prev_node_) +{ + // Iterates the list and finds the first free block with enough space + Free_List_Node *node = fl->head; + Free_List_Node *prev_node = NULL; + + size_t padding = 0; + + while (node) { + padding = calc_padding_with_header( + (uintptr_t)node, (uintptr_t)alignment, sizeof(Free_List_Header)); + size_t required_space = size + padding; + if (node->block_size >= required_space) { + break; + } + prev_node = node; + node = node->next; + } + if (padding_) + *padding_ = padding; + if (prev_node_) + *prev_node_ = prev_node; + return node; +} + +static void *free_list_alloc_aligned(Free_List *fl, size_t size, + size_t alignment) +{ + + size_t padding = 0; + Free_List_Node *prev_node = NULL; + Free_List_Node *node = NULL; + size_t alignment_padding, required_space, remaining; + Free_List_Header *header_ptr; + + if (size < sizeof(Free_List_Node)) { + size = sizeof(Free_List_Node); + } + + if (alignment < 8) { + alignment = 8; + } + + size_t aligned_size = (size + alignment - 1) & ~(alignment - 1); + + node = + free_list_find_first(fl, aligned_size, alignment, &padding, &prev_node); + if (!node) { + assert(0 && "Free list has no free memory"); + return NULL; + } + + alignment_padding = padding - sizeof(Free_List_Header); + required_space = aligned_size + padding; + remaining = node->block_size - required_space; + + if (remaining > 0) { + Free_List_Node *new_node = + (Free_List_Node *)((char *)node + required_space); + new_node->block_size = remaining; + free_list_node_insert(&fl->head, node, new_node); + } + + free_list_node_remove(&fl->head, prev_node, node); + + header_ptr = (Free_List_Header *)((char *)node + alignment_padding); + header_ptr->block_size = required_space; + header_ptr->padding = alignment_padding; + + fl->used += required_space; + + void *ptr = (void *)((char *)header_ptr + sizeof(Free_List_Header)); + + return memset(ptr, 0x00, aligned_size); +} + +static void free_list_coalescence(Free_List *fl, Free_List_Node *prev_node, + Free_List_Node *free_node); + +static void free_list_free_aligned(Free_List *fl, void *ptr) +{ + Free_List_Header *header; + Free_List_Node *free_node; + Free_List_Node *node; + Free_List_Node *prev_node = NULL; + + if (!ptr) + return; + + header = (Free_List_Header *)((char *)ptr - sizeof(Free_List_Header)); + free_node = (Free_List_Node *)header; + free_node->block_size = header->block_size + header->padding; + free_node->next = NULL; + + node = fl->head; + while (node) { + if (ptr < (void *)node) { + free_list_node_insert(&fl->head, prev_node, free_node); + break; + } + prev_node = node; + node = node->next; + } + + fl->used -= free_node->block_size; + + free_list_coalescence(fl, prev_node, free_node); +} + +static void free_list_coalescence(Free_List *fl, Free_List_Node *prev_node, + Free_List_Node *free_node) +{ + if (free_node->next != NULL && + (void *)((char *)free_node + free_node->block_size) == + free_node->next) { + free_node->block_size += free_node->next->block_size; + free_list_node_remove(&fl->head, free_node, free_node->next); + } + + if (prev_node && prev_node->next != NULL && + (void *)((char *)prev_node + prev_node->block_size) == free_node) { + prev_node->block_size += free_node->next->block_size; + free_list_node_remove(&fl->head, prev_node, free_node); + } +} + +void *free_list_alloc(void *context, size_t size) +{ + return free_list_alloc_aligned(context, size, DEFAULT_ALIGNMENT); +} + +void free_list_free(void *context, void *ptr) +{ + free_list_free_aligned(context, ptr); +} diff --git a/src/bst.c b/src/bst.c index d188f0c..2f2e7c3 100644 --- a/src/bst.c +++ b/src/bst.c @@ -1,7 +1,7 @@ /* * BSD 2-Clause License * - * Copyright (c) 2023 Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025 Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/config.c b/src/config.c index 0842806..3c76b34 100644 --- a/src/config.c +++ b/src/config.c @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/ev.c b/src/ev.c index 6b5d12a..0ba1a27 100644 --- a/src/ev.c +++ b/src/ev.c @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -38,7 +38,6 @@ #include "config.h" #include "ev.h" #include "memory.h" -#include "util.h" #if defined(EPOLL) @@ -116,21 +115,21 @@ static int ev_api_init(struct ev_ctx *ctx, int events_nr) return EV_OK; } -static void ev_api_destroy(struct ev_ctx *ctx) +static void ev_api_free(struct ev_ctx *ctx) { close(((struct epoll_api *)ctx->api)->fd); free_memory(((struct epoll_api *)ctx->api)->events); free_memory(ctx->api); } -static int ev_api_get_event_type(struct ev_ctx *ctx, int idx) +static int ev_api_get_event_mask(struct ev_ctx *ctx, int idx) { struct epoll_api *e_api = ctx->api; int events = e_api->events[idx].events; - int ev_mask = ctx->events_monitored[e_api->events[idx].data.fd].mask; + int ev_mask = ctx->monitored[e_api->events[idx].data.fd].mask; // We want to remember the previous events only if they're not of type // CLOSE or TIMER - int mask = ev_mask & (EV_CLOSEFD | EV_TIMERFD) ? ev_mask : EV_NONE; + int mask = ev_mask & (EV_CLOSEFD | EV_TIMERFD) ? ev_mask : EV_NONE; if (events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)) mask |= EV_DISCONNECT; if (events & EPOLLIN) @@ -146,19 +145,19 @@ static int ev_api_poll(struct ev_ctx *ctx, time_t timeout) return epoll_wait(e_api->fd, e_api->events, ctx->events_nr, timeout); } -static int ev_api_watch_fd(struct ev_ctx *ctx, int fd) +static int ev_api_watch(struct ev_ctx *ctx, int fd) { struct epoll_api *e_api = ctx->api; return epoll_add(e_api->fd, fd, EPOLLIN, NULL); } -static int ev_api_del_fd(struct ev_ctx *ctx, int fd) +static int ev_api_delete(struct ev_ctx *ctx, int fd) { struct epoll_api *e_api = ctx->api; return epoll_del(e_api->fd, fd); } -static int ev_api_register_event(struct ev_ctx *ctx, int fd, int mask) +static int ev_api_add_event(struct ev_ctx *ctx, int fd, int mask) { struct epoll_api *e_api = ctx->api; int op = 0; @@ -169,7 +168,7 @@ static int ev_api_register_event(struct ev_ctx *ctx, int fd, int mask) return epoll_add(e_api->fd, fd, op, NULL); } -static int ev_api_fire_event(struct ev_ctx *ctx, int fd, int mask) +static int ev_api_add(struct ev_ctx *ctx, int fd, int mask) { struct epoll_api *e_api = ctx->api; int op = 0; @@ -190,7 +189,7 @@ static inline struct ev *ev_api_fetch_event(const struct ev_ctx *ctx, int idx, int mask) { int fd = ((struct epoll_api *)ctx->api)->events[idx].data.fd; - return ctx->events_monitored + fd; + return ctx->monitored + fd; } #elif defined(POLL) @@ -213,31 +212,31 @@ static inline struct ev *ev_api_fetch_event(const struct ev_ctx *ctx, int idx, struct poll_api { int nfds; - int events_monitored; + int monitored; struct pollfd *fds; }; static int ev_api_init(struct ev_ctx *ctx, int events_nr) { - struct poll_api *p_api = try_alloc(sizeof(*p_api)); - p_api->nfds = 0; - p_api->fds = try_calloc(events_nr, sizeof(struct pollfd)); - p_api->events_monitored = events_nr; - ctx->api = p_api; - ctx->maxfd = events_nr; + struct poll_api *p_api = try_alloc(sizeof(*p_api)); + p_api->nfds = 0; + p_api->fds = try_calloc(events_nr, sizeof(struct pollfd)); + p_api->monitored = events_nr; + ctx->api = p_api; + ctx->maxfd = events_nr; return EV_OK; } -static void ev_api_destroy(struct ev_ctx *ctx) +static void ev_api_free(struct ev_ctx *ctx) { free_memory(((struct poll_api *)ctx->api)->fds); free_memory(ctx->api); } -static int ev_api_get_event_type(struct ev_ctx *ctx, int idx) +static int ev_api_get_event_mask(struct ev_ctx *ctx, int idx) { struct poll_api *p_api = ctx->api; - int ev_mask = ctx->events_monitored[p_api->fds[idx].fd].mask; + int ev_mask = ctx->monitored[p_api->fds[idx].fd].mask; // We want to remember the previous events only if they're not of type // CLOSE or TIMER int mask = ev_mask & (EV_CLOSEFD | EV_TIMERFD) ? ev_mask : 0; @@ -265,21 +264,21 @@ static int ev_api_poll(struct ev_ctx *ctx, time_t timeout) * resize is needed cause the number of fds have reached the length of the fds * array, we must increase its size. */ -static int ev_api_watch_fd(struct ev_ctx *ctx, int fd) +static int ev_api_watch(struct ev_ctx *ctx, int fd) { struct poll_api *p_api = ctx->api; p_api->fds[p_api->nfds].fd = fd; p_api->fds[p_api->nfds].events = POLLIN; p_api->nfds++; - if (p_api->nfds >= p_api->events_monitored) { - p_api->events_monitored *= 2; - p_api->fds = try_realloc(p_api->fds, p_api->events_monitored * - sizeof(struct pollfd)); + if (p_api->nfds >= p_api->monitored) { + p_api->monitored *= 2; + p_api->fds = + try_realloc(p_api->fds, p_api->monitored * sizeof(struct pollfd)); } return EV_OK; } -static int ev_api_del_fd(struct ev_ctx *ctx, int fd) +static int ev_api_delete(struct ev_ctx *ctx, int fd) { struct poll_api *p_api = ctx->api; for (int i = 0; i < p_api->nfds; ++i) { @@ -297,9 +296,9 @@ static int ev_api_del_fd(struct ev_ctx *ctx, int fd) } /* - * We have to check for resize even here just like ev_api_watch_fd. + * We have to check for resize even here just like ev_api_watch. */ -static int ev_api_register_event(struct ev_ctx *ctx, int fd, int mask) +static int ev_api_add_event(struct ev_ctx *ctx, int fd, int mask) { struct poll_api *p_api = ctx->api; p_api->fds[p_api->nfds].fd = fd; @@ -308,15 +307,15 @@ static int ev_api_register_event(struct ev_ctx *ctx, int fd, int mask) if (mask & EV_WRITE) p_api->fds[p_api->nfds].events |= POLLOUT; p_api->nfds++; - if (p_api->nfds >= p_api->events_monitored) { - p_api->events_monitored *= 2; - p_api->fds = try_realloc(p_api->fds, p_api->events_monitored * - sizeof(struct pollfd)); + if (p_api->nfds >= p_api->monitored) { + p_api->monitored *= 2; + p_api->fds = + try_realloc(p_api->fds, p_api->monitored * sizeof(struct pollfd)); } return EV_OK; } -static int ev_api_fire_event(struct ev_ctx *ctx, int fd, int mask) +static int ev_api_add(struct ev_ctx *ctx, int fd, int mask) { struct poll_api *p_api = ctx->api; for (int i = 0; i < p_api->nfds; ++i) { @@ -335,7 +334,7 @@ static int ev_api_fire_event(struct ev_ctx *ctx, int fd, int mask) static inline struct ev *ev_api_fetch_event(const struct ev_ctx *ctx, int idx, int mask) { - return ctx->events_monitored + ((struct poll_api *)ctx->api)->fds[idx].fd; + return ctx->monitored + ((struct poll_api *)ctx->api)->fds[idx].fd; } #elif defined(SELECT) @@ -376,12 +375,12 @@ static int ev_api_init(struct ev_ctx *ctx, int events_nr) return EV_OK; } -static void ev_api_destroy(struct ev_ctx *ctx) { free_memory(ctx->api); } +static void ev_api_free(struct ev_ctx *ctx) { free_memory(ctx->api); } -static int ev_api_get_event_type(struct ev_ctx *ctx, int idx) +static int ev_api_get_event_mask(struct ev_ctx *ctx, int idx) { struct select_api *s_api = ctx->api; - int ev_mask = ctx->events_monitored[idx].mask; + int ev_mask = ctx->monitored[idx].mask; // We want to remember the previous events only if they're not of type // CLOSE or TIMER int mask = ev_mask & (EV_CLOSEFD | EV_TIMERFD) ? ev_mask : 0; @@ -419,7 +418,7 @@ static int ev_api_poll(struct ev_ctx *ctx, time_t timeout) return ctx->maxfd + 1; } -static int ev_api_watch_fd(struct ev_ctx *ctx, int fd) +static int ev_api_watch(struct ev_ctx *ctx, int fd) { struct select_api *s_api = ctx->api; FD_SET(fd, &s_api->rfds); @@ -429,7 +428,7 @@ static int ev_api_watch_fd(struct ev_ctx *ctx, int fd) return EV_OK; } -static int ev_api_del_fd(struct ev_ctx *ctx, int fd) +static int ev_api_delete(struct ev_ctx *ctx, int fd) { struct select_api *s_api = ctx->api; if (FD_ISSET(fd, &s_api->rfds)) @@ -448,7 +447,7 @@ static int ev_api_del_fd(struct ev_ctx *ctx, int fd) return EV_OK; } -static int ev_api_register_event(struct ev_ctx *ctx, int fd, int mask) +static int ev_api_add_event(struct ev_ctx *ctx, int fd, int mask) { struct select_api *s_api = ctx->api; if (mask & EV_READ) @@ -461,7 +460,7 @@ static int ev_api_register_event(struct ev_ctx *ctx, int fd, int mask) return EV_OK; } -static int ev_api_fire_event(struct ev_ctx *ctx, int fd, int mask) +static int ev_api_add(struct ev_ctx *ctx, int fd, int mask) { struct select_api *s_api = ctx->api; if (mask & EV_READ) @@ -478,7 +477,7 @@ static int ev_api_fire_event(struct ev_ctx *ctx, int fd, int mask) static inline struct ev *ev_api_fetch_event(const struct ev_ctx *ctx, int idx, int mask) { - return ctx->events_monitored + idx; + return ctx->monitored + idx; } #elif defined(KQUEUE) @@ -513,48 +512,48 @@ static int ev_api_init(struct ev_ctx *ctx, int events_nr) return EV_OK; } -static void ev_api_destroy(struct ev_ctx *ctx) +static void ev_api_free(struct ev_ctx *ctx) { close(((struct kqueue_api *)ctx->api)->fd); free_memory(((struct kqueue_api *)ctx->api)->events); free_memory(ctx->api); } -static int ev_api_get_event_type(struct ev_ctx *ctx, int idx) +static int ev_api_get_event_mask(struct ev_ctx *ctx, int idx) { struct kqueue_api *k_api = ctx->api; - int events = k_api->events[idx].flags; - int ev_mask = ctx->events_monitored[k_api->events[idx].ident].mask; + int events = k_api->events[idx].filter; + int ev_mask = ctx->monitored[k_api->events[idx].ident].mask; // We want to remember the previous events only if they're not of type // CLOSE or TIMER - int mask = ev_mask & (EV_CLOSEFD | EV_TIMERFD) ? ev_mask : EV_NONE; + int mask = ev_mask & (EV_CLOSEFD | EV_TIMERFD) ? ev_mask : EV_NONE; if (events & (EV_EOF | EV_ERROR)) mask |= EV_DISCONNECT; if (events & EVFILT_READ) mask |= EV_READ; if (events & EVFILT_WRITE) mask |= EV_WRITE; + ctx->monitored[k_api->events[idx].ident].mask = EV_NONE; return mask; } static int ev_api_poll(struct ev_ctx *ctx, time_t timeout) { - struct kqueue_api *k_api = ctx->api; - struct timespec ts_timeout; - ts_timeout.tv_sec = timeout; - ts_timeout.tv_nsec = 0; - int err = - kevent(k_api->fd, NULL, 0, k_api->events, ctx->maxevents, &ts_timeout); + struct kqueue_api *k_api = ctx->api; + const struct timespec ts = {.tv_sec = timeout < 0 ? 0 : timeout, + .tv_nsec = 0}; + const struct timespec *ts_ptr = timeout < 0 ? NULL : &ts; + int err = kevent(k_api->fd, NULL, 0, k_api->events, ctx->maxevents, ts_ptr); if (err < 0) return -EV_ERR; return err; } -static int ev_api_del_fd(struct ev_ctx *ctx, int fd) +static int ev_api_delete(struct ev_ctx *ctx, int fd) { struct kqueue_api *k_api = ctx->api; struct kevent ke; - int ev_mask = ctx->events_monitored[fd].mask; + int ev_mask = ctx->monitored[fd].mask; int mask = 0; if (ev_mask & EV_READ) mask |= EVFILT_READ; @@ -568,7 +567,7 @@ static int ev_api_del_fd(struct ev_ctx *ctx, int fd) return EV_OK; } -static int ev_api_register_event(struct ev_ctx *ctx, int fd, int mask) +static int ev_api_add_event(struct ev_ctx *ctx, int fd, int mask) { struct kqueue_api *k_api = ctx->api; struct kevent ke; @@ -583,21 +582,22 @@ static int ev_api_register_event(struct ev_ctx *ctx, int fd, int mask) return EV_OK; } -static int ev_api_watch_fd(struct ev_ctx *ctx, int fd) +static int ev_api_watch(struct ev_ctx *ctx, int fd) { - return ev_api_register_event(ctx, fd, EV_READ); + return ev_api_add_event(ctx, fd, EV_READ); } -static int ev_api_fire_event(struct ev_ctx *ctx, int fd, int mask) +static int ev_api_add(struct ev_ctx *ctx, int fd, int mask) { struct kqueue_api *k_api = ctx->api; struct kevent ke; - int op = 0; + int flags = EV_ADD | EV_ONESHOT; + int op = 0; if (mask & (EV_READ | EV_EVENTFD)) op |= EVFILT_READ; if (mask & EV_WRITE) op |= EVFILT_WRITE; - EV_SET(&ke, fd, op, EV_ADD | EV_ENABLE, 0, 0, NULL); + EV_SET(&ke, fd, op, flags, 0, 0, NULL); if (kevent(k_api->fd, &ke, 1, NULL, 0, NULL) == -1) return -EV_ERR; return EV_OK; @@ -612,13 +612,13 @@ static inline struct ev *ev_api_fetch_event(const struct ev_ctx *ctx, int idx, { (void)mask; // silence compiler warning int fd = ((struct kqueue_api *)ctx->api)->events[idx].ident; - return ctx->events_monitored + fd; + return ctx->monitored + fd; } #endif // KQUEUE /* - * Process the event at the position idx in the events_monitored array. Read or + * Process the event at the position idx in the monitored array. Read or * write events can be executed on the same iteration, differentiating just * on EV_CLOSEFD or EV_EVENTFD. * Returns the number of fired callbacks. @@ -657,9 +657,10 @@ static int ev_process_event(struct ev_ctx *ctx, int idx, int mask) ++fired; } if (mask & EV_WRITE) { - if (!fired || e->wcallback != e->rcallback) { + if (!fired || (e->wcallback && (e->wcallback != e->rcallback))) { e->wcallback(ctx, e->wdata); ++fired; + e->wcallback = NULL; } } } @@ -672,8 +673,7 @@ static int ev_process_event(struct ev_ctx *ctx, int idx, int mask) * context. */ static void ev_add_monitored(struct ev_ctx *ctx, int fd, int mask, - void (*callback)(struct ev_ctx *, void *), - void *ptr) + ev_callback cb, void *ptr) { /* * TODO check for fd <= 1024 if using SELECT @@ -684,27 +684,27 @@ static void ev_add_monitored(struct ev_ctx *ctx, int fd, int mask, int i = ctx->maxevents; ctx->maxevents = fd; if (fd > ctx->events_nr) { - ctx->events_monitored = try_realloc(ctx->events_monitored, - (fd + 1) * sizeof(struct ev)); + ctx->monitored = + try_realloc(ctx->monitored, (fd + 1) * sizeof(struct ev)); for (; i < ctx->maxevents; ++i) - ctx->events_monitored[i].mask = EV_NONE; + ctx->monitored[i].mask = EV_NONE; } } - ctx->events_monitored[fd].fd = fd; - ctx->events_monitored[fd].mask |= mask; + ctx->monitored[fd].fd = fd; + ctx->monitored[fd].mask |= mask; if (mask & EV_READ) { - ctx->events_monitored[fd].rdata = ptr; - ctx->events_monitored[fd].rcallback = callback; + ctx->monitored[fd].rdata = ptr; + ctx->monitored[fd].rcallback = cb; } if (mask & EV_WRITE) { - ctx->events_monitored[fd].wdata = ptr; - ctx->events_monitored[fd].wcallback = callback; + ctx->monitored[fd].wdata = ptr; + ctx->monitored[fd].wcallback = cb; } } -static inline int ev_get_event_type(struct ev_ctx *ctx, int idx) +static inline int ev_get_event_mask(struct ev_ctx *ctx, int idx) { - return ev_api_get_event_type(ctx, idx); + return ev_api_get_event_mask(ctx, idx); } int ev_init(struct ev_ctx *ctx, int events_nr) @@ -712,23 +712,23 @@ int ev_init(struct ev_ctx *ctx, int events_nr) int err = ev_api_init(ctx, events_nr); if (err < 0) return err; - ctx->stop = 0; - ctx->fired_events = 0; - ctx->maxevents = events_nr; - ctx->events_nr = events_nr; - ctx->events_monitored = try_calloc(events_nr, sizeof(struct ev)); + ctx->stop = 0; + ctx->fired_events = 0; + ctx->maxevents = events_nr; + ctx->events_nr = events_nr; + ctx->monitored = try_calloc(events_nr, sizeof(struct ev)); return EV_OK; } -void ev_destroy(struct ev_ctx *ctx) +void ev_free(struct ev_ctx *ctx) { for (int i = 0; i < ctx->maxevents; ++i) { - if (!(ctx->events_monitored[i].mask & EV_CLOSEFD) && - ctx->events_monitored[i].mask != EV_NONE) - ev_del_fd(ctx, ctx->events_monitored[i].fd); + if (!(ctx->monitored[i].mask & EV_CLOSEFD) && + ctx->monitored[i].mask != EV_NONE) + ev_delete(ctx, ctx->monitored[i].fd); } - free_memory(ctx->events_monitored); - ev_api_destroy(ctx); + free_memory(ctx->monitored); + ev_api_free(ctx); } int ev_poll(struct ev_ctx *ctx, time_t timeout) @@ -750,14 +750,11 @@ int ev_run(struct ev_ctx *ctx) */ n = ev_poll(ctx, -1); if (n < 0) { - /* Signals to all threads. Ignore it for now */ - if (errno == EINTR) - continue; /* Error occured, break the loop */ break; } for (int i = 0; i < n; ++i) { - events = ev_get_event_type(ctx, i); + events = ev_get_event_mask(ctx, i); ctx->fired_events += ev_process_event(ctx, i, events); } } @@ -766,16 +763,16 @@ int ev_run(struct ev_ctx *ctx) void ev_stop(struct ev_ctx *ctx) { ctx->stop = 1; } -int ev_watch_fd(struct ev_ctx *ctx, int fd, int mask) +int ev_watch(struct ev_ctx *ctx, int fd, int mask) { ev_add_monitored(ctx, fd, mask, NULL, NULL); - return ev_api_watch_fd(ctx, fd); + return ev_api_watch(ctx, fd); } -int ev_del_fd(struct ev_ctx *ctx, int fd) +int ev_delete(struct ev_ctx *ctx, int fd) { - memset(ctx->events_monitored + fd, 0x00, sizeof(struct ev)); - return ev_api_del_fd(ctx, fd); + memset(ctx->monitored + fd, 0x00, sizeof(struct ev)); + return ev_api_delete(ctx, fd); } /* @@ -790,12 +787,12 @@ int ev_del_fd(struct ev_ctx *ctx, int fd) * - callback: is a function pointer to the routine we want to execute * - data: an opaque pointer to the arguments for the callback. */ -int ev_register_event(struct ev_ctx *ctx, int fd, int mask, - void (*callback)(struct ev_ctx *, void *), void *data) +int ev_add_event(struct ev_ctx *ctx, int fd, int mask, ev_callback cb, + void *data) { - ev_add_monitored(ctx, fd, mask, callback, data); + ev_add_monitored(ctx, fd, mask, cb, data); int ret = 0; - ret = ev_api_register_event(ctx, fd, mask); + ret = ev_api_add_event(ctx, fd, mask); if (ret < 0) return -EV_ERR; if (mask & EV_EVENTFD) @@ -812,9 +809,8 @@ int ev_register_event(struct ev_ctx *ctx, int fd, int mask, * loop, specifying, seconds and/or nanoseconds defining how often the callback * should be executed. */ -int ev_register_cron(struct ev_ctx *ctx, - void (*callback)(struct ev_ctx *, void *), void *data, - long long s, long long ns) +int ev_add_cron(struct ev_ctx *ctx, ev_callback cb, void *data, long long s, + long long ns) { #ifdef __linux__ struct itimerspec timer; @@ -830,14 +826,14 @@ int ev_register_cron(struct ev_ctx *ctx, return -EV_ERR; // Add the timer to the event loop - ev_add_monitored(ctx, timerfd, EV_TIMERFD | EV_READ, callback, data); - return ev_api_watch_fd(ctx, timerfd); + ev_add_monitored(ctx, timerfd, EV_TIMERFD | EV_READ, cb, data); + return ev_api_watch(ctx, timerfd); #else struct kqueue_api *k_api = ctx->api; // milliseconds unsigned period = (s * 1000) + (ns / 100); int fd = socket(AF_INET, SOCK_STREAM, 0); - ev_add_monitored(ctx, fd, EV_TIMERFD | EV_READ, callback, data); + ev_add_monitored(ctx, fd, EV_TIMERFD | EV_READ, cb, data); struct kevent ke; EV_SET(&ke, fd, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, period, 0); if (kevent(k_api->fd, &ke, 1, NULL, 0, NULL) == -1) @@ -858,12 +854,11 @@ int ev_register_cron(struct ev_ctx *ctx, * - callback: is a function pointer to the routine we want to execute * - data: an opaque pointer to the arguments for the callback. */ -int ev_fire_event(struct ev_ctx *ctx, int fd, int mask, - void (*callback)(struct ev_ctx *, void *), void *data) +int ev_oneshot(struct ev_ctx *ctx, int fd, int mask, ev_callback cb, void *data) { int ret = 0; - ev_add_monitored(ctx, fd, mask, callback, data); - ret = ev_api_fire_event(ctx, fd, mask); + ev_add_monitored(ctx, fd, mask, cb, data); + ret = ev_api_add(ctx, fd, mask); if (ret < 0) return -EV_ERR; if (mask & EV_EVENTFD) { diff --git a/src/handlers.c b/src/handlers.c index a20c664..f11e367 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,37 +26,35 @@ */ #include "handlers.h" +#include "arena.h" #include "config.h" #include "logging.h" #include "memory.h" #include "mqtt.h" #include "server.h" #include "sol_internal.h" +#include "util.h" #include /* Prototype for a command handler */ -typedef int handler(struct io_event *); +typedef int handler(Connection_Context *); /* Command handler, each one have responsibility over a defined command packet */ -static int connect_handler(struct io_event *); -static int disconnect_handler(struct io_event *); -static int subscribe_handler(struct io_event *); -static int unsubscribe_handler(struct io_event *); -static int publish_handler(struct io_event *); -static int puback_handler(struct io_event *); -static int pubrec_handler(struct io_event *); -static int pubrel_handler(struct io_event *); -static int pubcomp_handler(struct io_event *); -static int pingreq_handler(struct io_event *); +static int connect_handler(Connection_Context *); +static int disconnect_handler(Connection_Context *); +static int subscribe_handler(Connection_Context *); +static int unsubscribe_handler(Connection_Context *); +static int publish_handler(Connection_Context *); +static int puback_handler(Connection_Context *); +static int pubrec_handler(Connection_Context *); +static int pubrel_handler(Connection_Context *); +static int pubcomp_handler(Connection_Context *); +static int pingreq_handler(Connection_Context *); -static void session_init(struct client_session *, const char *); +static void session_init(Session *, const char *); -static struct client_session *client_session_alloc(const char *); - -static unsigned next_free_mid(struct client_session *); - -static void inflight_msg_init(struct inflight_msg *, struct mqtt_packet *); +static unsigned next_free_mid(Session *); /* Command handler mapped usign their position paired with their type */ static handler *handlers[15] = {NULL, @@ -81,56 +79,19 @@ static handler *handlers[15] = {NULL, * ========================= */ -static void session_free(const struct ref *refcount) +static void session_init(Session *session, const char *cid) { - struct client_session *session = - container_of(refcount, struct client_session, refcount); - list_destroy(session->subscriptions, 0); - list_destroy(session->outgoing_msgs, 0); - if (has_inflight(session)) { - for (int i = 0; i < MAX_INFLIGHT_MSGS; ++i) { - if (session->i_msgs[i].packet) - DECREF(session->i_msgs[i].packet, struct mqtt_packet); - } - } - free_memory(session->i_acks); - free_memory(session->i_msgs); - free_memory(session); -} - -static void session_init(struct client_session *session, const char *session_id) -{ - session->inflights = ATOMIC_VAR_INIT(0); - session->next_free_mid = 1; + session->inflights = 0; + session->next_mid = 1; session->subscriptions = list_new(NULL); - session->outgoing_msgs = list_new(NULL); - snprintf(session->session_id, MQTT_CLIENT_ID_LEN, "%s", session_id); - session->i_acks = try_calloc(MAX_INFLIGHT_MSGS, sizeof(time_t)); - session->i_msgs = - try_calloc(MAX_INFLIGHT_MSGS, sizeof(struct inflight_msg)); - session->refcount = (struct ref){session_free, 0}; + snprintf(session->cid, sizeof(session->cid), "%s", cid); } -static struct client_session *client_session_alloc(const char *session_id) +static inline unsigned next_free_mid(Session *session) { - struct client_session *session = try_alloc(sizeof(*session)); - session_init(session, session_id); - return session; -} - -static inline unsigned next_free_mid(struct client_session *session) -{ - if (session->next_free_mid == MAX_INFLIGHT_MSGS) - session->next_free_mid = 1; - return session->next_free_mid++; -} - -static inline void inflight_msg_init(struct inflight_msg *imsg, - struct mqtt_packet *p) -{ - imsg->seen = time(NULL); - imsg->packet = p; - imsg->qos = p->header.bits.qos; + if (session->next_mid == MAX_INFLIGHT_MSGS) + session->next_mid = 1; + return session->next_mid++; } /* @@ -141,37 +102,35 @@ static inline void inflight_msg_init(struct inflight_msg *imsg, * Returns the number of publish done or an error code in case of conditions * that requires de-allocation of the pkt argument occurs. */ -int publish_message(struct mqtt_packet *pkt, const struct topic *t) +void publish_message(MQTT_Packet *packet, const Topic *topic, + Arena_Allocator *allocator) { + unsigned short mid = 0; + unsigned char qos = packet->header.bits.qos; - bool all_at_most_once = true; - size_t len = 0; - unsigned short mid = 0; - unsigned char qos = pkt->header.bits.qos; -#if THREADSNR > 0 - pthread_mutex_lock(&mutex); -#endif - int count = HASH_COUNT(t->subscribers); - - if (count == 0) { - INCREF(pkt, struct mqtt_packet); - goto exit; - } + if (HASH_COUNT(topic->subscribers) == 0) + return; // first run check - struct subscriber *sub, *dummy; - HASH_ITER(hh, t->subscribers, sub, dummy) + Subscriber *subscriber, *dummy; + HASH_ITER(hh, topic->subscribers, subscriber, dummy) { - struct client_session *s = sub->session; - struct client *sc = NULL; - HASH_FIND_STR(server.clients_map, s->session_id, sc); + Session *subscriber_session = subscriber->session; + Connection_Context *subscriber_ctx = NULL; + HASH_FIND_STR(server.contexts, subscriber_session->cid, subscriber_ctx); + /* * Update QoS according to subscriber's one, following MQTT * rules: The min between the original QoS and the subscriber * QoS */ - pkt->header.bits.qos = qos >= sub->granted_qos ? sub->granted_qos : qos; - len = mqtt_size(pkt, NULL); // override len, no ID set in QoS 0 + packet->header.bits.qos = + qos >= subscriber->granted_qos ? subscriber->granted_qos : qos; + + // QoS 0 disconnected + if (!subscriber_ctx && subscriber->granted_qos == AT_MOST_ONCE) + continue; + /* * if QoS 0 * @@ -179,92 +138,73 @@ int publish_message(struct mqtt_packet *pkt, const struct topic *t) * correct QoS value (0) and packet identifier to (0) as * specified by MQTT specs */ - pkt->publish.pkt_id = 0; + packet->publish.id = 0; /* * if QoS > 0 we set packet identifier and track the inflight * message, proceed with the publish towards online subscriber. */ - if (pkt->header.bits.qos > AT_MOST_ONCE) { - mid = next_free_mid(s); - pkt->publish.pkt_id = mid; - INCREF(pkt, struct mqtt_packet); + if (packet->header.bits.qos > AT_MOST_ONCE) { + + mid = next_free_mid(subscriber_session); + packet->publish.id = mid; /* * If offline, we must enqueue messages in the inflight queue * of the client, they will be sent out only in case of a * clean_session == false connection */ - if (!sc || sc->online == false) { - if (s->clean_session == false) { - list_push(s->outgoing_msgs, pkt); - all_at_most_once = false; - INCREF(pkt, struct mqtt_packet); - inflight_msg_init(&s->i_msgs[mid], pkt); - s->i_acks[mid] = time(NULL); - ++s->inflights; + if (!subscriber_ctx || !subscriber_ctx->online) { + if (!subscriber_session->clean_session) { + subscriber_session->i_msgs[mid].lastack_at = time(NULL); + subscriber_session->i_msgs[mid].packet = packet; + ++subscriber_session->inflights; } continue; } -#if THREADSNR > 0 - pthread_mutex_lock(&sc->mutex); -#endif /* * The subscriber client is marked as online, so we proceed to * set the inflight messages according to the QoS level required * and write back the payload */ - inflight_msg_init(&sc->session->i_msgs[mid], pkt); - sc->session->i_acks[mid] = time(NULL); - ++sc->session->inflights; -#if THREADSNR > 0 - pthread_mutex_unlock(&sc->mutex); -#endif - all_at_most_once = false; + subscriber_ctx->session->i_msgs[mid].lastack_at = time(NULL); + subscriber_ctx->session->i_msgs[mid].packet = packet; + ++subscriber_ctx->session->inflights; } -#if THREADSNR > 0 - pthread_mutex_lock(&sc->mutex); -#endif - mqtt_pack(pkt, sc->wbuf + sc->towrite); - sc->towrite += len; -#if THREADSNR > 0 - pthread_mutex_unlock(&sc->mutex); -#endif + + subscriber_ctx->write_total += mqtt_write( + packet, subscriber_ctx->send_buf + subscriber_ctx->write_total); // Schedule a write for the current subscriber on the next event cycle - enqueue_event_write(sc); + enqueue_event_write(subscriber_ctx); info.messages_sent++; log_debug( "Sending PUBLISH to %s (d%i, q%u, r%i, m%u, %s, ... (%i bytes))", - sc->client_id, pkt->header.bits.dup, pkt->header.bits.qos, - pkt->header.bits.retain, pkt->publish.pkt_id, pkt->publish.topic, - pkt->publish.payloadlen); + subscriber_ctx->cid, packet->header.bits.dup, + packet->header.bits.qos, packet->header.bits.retain, + packet->publish.id, packet->publish.topic, + packet->publish.payloadlen); } - - // add return code - if (all_at_most_once == true) - count = 0; - -exit: - -#if THREADSNR > 0 - pthread_mutex_unlock(&mutex); -#endif - return count; } /* - * Check if a topic match a wildcard subscription. It works with + and # as + * Check if a topic matches a wildcard subscription. It works with + and # as * well */ -static inline int match_subscription(const char *topic, const char *wtopic, - bool multilevel) +static int match_subscription(const char *topic, + const Subscription *subscription) { - size_t len = strlen(wtopic); + bool multilevel = subscription->multilevel; + const char *wtopic = subscription->topic; + size_t len = strlen(wtopic); int i = 0, j = 0; bool found = false; char *ptopic = (char *)topic; + + if (!ptopic) + return -SOL_ERR; + /* * Cycle through the wildcard topic, char by char, seeking for '+' char and * at the same time assuring that every char is equal in the topic as well, @@ -272,14 +212,13 @@ static inline int match_subscription(const char *topic, const char *wtopic, */ while (i < len && wtopic[i]) { j = 0; - for (; i < len; ++i) { + for (; i < len; ++i, ++j) { if (wtopic[i] == '+') { found = true; break; - } else if (!ptopic || (wtopic[i] != ptopic[j])) { + } else if (wtopic[i] != ptopic[j]) { return -SOL_ERR; } - j++; } /* * Get a pointer to the next '/', called two times because we want to @@ -306,57 +245,54 @@ static inline int match_subscription(const char *topic, const char *wtopic, * Command handlers */ -static void set_connack(struct client *c, unsigned char rc, unsigned sp) +static void set_connack(Connection_Context *c, unsigned char rc, + unsigned session_present) { - unsigned char connect_flags = 0 | (sp & 0x1) << 0; + unsigned char connect_flags = 0 | (session_present & 0x1) << 0; - struct mqtt_packet response = { - .header = {.byte = CONNACK_B}, - .connack = (struct mqtt_connack){.byte = connect_flags, .rc = rc}}; - mqtt_pack(&response, c->wbuf + c->towrite); - c->towrite += MQTT_ACK_LEN; + MQTT_Packet response = { + .header = {.byte = CONNACK_B}, + .connack = (MQTT_Connack){.byte = connect_flags, .rc = rc}}; + c->write_total += mqtt_write(&response, c->send_buf + c->write_total); /* * If a session was present and the connected client have disabled the * clean session flag, we have to take care of the outgoing messages * pending, strictly after the CONNACK encoding */ - if (c->clean_session == false && sp == 1) { - log_info("Resuming session for %s", c->client_id); + if (c->clean_session == false && session_present == 1) { + log_info("Resuming session for %s", c->cid); /* * If there's already some subscriptions and pending messages, * empty the queue */ // TODO check for write buffer size exceed - if (list_size(c->session->outgoing_msgs) > 0) { - size_t len = 0; - list_foreach(item, c->session->outgoing_msgs) - { - len = mqtt_size(item->data, NULL); - mqtt_pack(item->data, c->wbuf + c->towrite); - c->towrite += len; + if (has_inflight(c->session)) { + size_t len = 0; + Inflight_Message *message = NULL; + for (int i = 0; i < MAX_INFLIGHT_MSGS; + ++i, c->write_total += len, message = &c->session->i_msgs[i]) { + if (!message->packet) + continue; + len = mqtt_write(message->packet, c->send_buf + c->write_total); } - // We want to clean up the queue after the payload set - list_clear(c->session->outgoing_msgs, 0); } } } -static int connect_handler(struct io_event *e) +static int connect_handler(Connection_Context *c) { - unsigned session_present = 0; - struct mqtt_connect *c = &e->data.connect; - struct client *cc = e->client; + MQTT_Connect *packet = &c->data.connect; - if (cc->connected == true) { + if (c->connected == true) { /* * Already connected client, 2 CONNECT packet should be interpreted as * a violation of the protocol, causing disconnection of the client */ log_info("Received double CONNECT from %s, disconnecting client", - c->payload.client_id); - goto clientdc; + packet->payload.client_id); + goto e_client_dc; } /* @@ -364,13 +300,14 @@ static int connect_handler(struct io_event *e) * username:password pair match in the authentications table */ if (conf->allow_anonymous == false) { - if (c->bits.username == 0 || c->bits.password == 0) - goto bad_auth; + if (packet->bits.username == 0 || packet->bits.password == 0) + goto e_bad_auth; else { struct authentication *auth = NULL; - HASH_FIND_STR(server.auths, (char *)c->payload.username, auth); - if (!auth || !check_passwd((char *)c->payload.password, auth->salt)) - goto bad_auth; + HASH_FIND_STR(server.auths, (char *)packet->payload.username, auth); + if (!auth || + !check_passwd((char *)packet->payload.password, auth->salt)) + goto e_bad_auth; } } @@ -378,222 +315,207 @@ static int connect_handler(struct io_event *e) * No client ID and clean_session == false? you're not authorized, we don't * know who you are */ - if (!c->payload.client_id[0] && c->bits.clean_session == false) - goto not_authorized; + if (!packet->payload.client_id[0] && packet->bits.clean_session == false) + goto e_not_authorized; /* * Check for client ID, if not present generate a random ID, otherwise add * the client to the sessions map if not already present */ - if (!c->payload.client_id[0]) - generate_random_id((char *)c->payload.client_id); + if (!packet->payload.client_id[0]) + generate_random_id((char *)packet->payload.client_id); /* * Add the new connected client to the global map, if it is already * connected, kick him out accordingly to the MQTT v3.1.1 specs. */ - snprintf(cc->client_id, MQTT_CLIENT_ID_LEN, "%s", c->payload.client_id); + snprintf(c->cid, sizeof(c->cid), "%s", packet->payload.client_id); -#if THREADSNR > 0 - pthread_mutex_lock(&mutex); -#endif // First we check if a session is present - HASH_FIND_STR(server.sessions, cc->client_id, cc->session); - if (cc->session && c->bits.clean_session == true) + HASH_FIND_STR(server.sessions, c->cid, c->session); + if (c->session && packet->bits.clean_session == true) // Clean session true, we have to clean old session, if any - HASH_DEL(server.sessions, cc->session); - else if (cc->session) + HASH_DEL(server.sessions, c->session); + else if (c->session) session_present = 1; - cc->connected = true; + c->connected = true; - log_info("New client connected as %s (c%i, k%u)", c->payload.client_id, - c->bits.clean_session, c->payload.keepalive); + log_info("New client connected as %s (c%i, k%u)", packet->payload.client_id, + packet->bits.clean_session, packet->payload.keepalive); /* * If no session was found or the client is a new connecting client or an * anonymous one, we create a session here */ - if (c->bits.clean_session == true || !cc->session) { - cc->session = client_session_alloc(cc->client_id); - INCREF(cc->session, struct client_session); - HASH_ADD_STR(server.sessions, session_id, cc->session); + if (packet->bits.clean_session || !c->session) { + c->session = + arena_alloc(&server.session_allocator, sizeof(*c->session)); + session_init(c->session, c->cid); + HASH_ADD_STR(server.sessions, cid, c->session); } - cc->session->clean_session = c->bits.clean_session; + c->session->clean_session = packet->bits.clean_session; // Let's track client on the global map to be used on publish - HASH_ADD_STR(server.clients_map, client_id, cc); -#if THREADSNR > 0 - pthread_mutex_unlock(&mutex); -#endif + HASH_ADD_STR(server.contexts, cid, c); // Add LWT topic and message if present - if (c->bits.will) { - cc->has_lwt = true; - const char *will_topic = (const char *)c->payload.will_topic; - const char *will_message = (const char *)c->payload.will_message; + if (packet->bits.will) { + const char *will_topic = (const char *)packet->payload.will_topic; + const char *will_message = (const char *)packet->payload.will_message; // TODO check for will_topic != NULL - struct topic *t = topic_store_get_or_put(server.store, will_topic); - if (!topic_store_contains(server.store, t->name)) - topic_store_put(server.store, t); + Topic *t = topic_repo_fetch_default(server.repo, will_topic); + if (!topic_repo_contains(server.repo, t->name)) + topic_repo_put(server.repo, t); // I'm sure that the string will be NUL terminated by unpack function - size_t msg_len = strlen(will_message); - size_t tpc_len = strlen(will_topic); - - cc->session->lwt_msg = (struct mqtt_packet){ - .header = (union mqtt_header){.byte = PUBLISH_B}, - .publish = (struct mqtt_publish){ - .pkt_id = 0, // placeholder - .topiclen = tpc_len, + size_t messagelen = strlen(will_message); + size_t topiclen = strlen(will_topic); + + // TODO move to arena + c->session->lwt = pool_alloc(&server.packet_allocator); + c->session->lwt->header.byte = PUBLISH_B; + c->session->lwt->publish = (MQTT_Publish){ + .id = 0, // placeholder + .topiclen = topiclen, .topic = (unsigned char *)try_strdup(will_topic), - .payloadlen = msg_len, - .payload = (unsigned char *)try_strdup(will_message)}}; + .payloadlen = messagelen, + .payload = (unsigned char *)try_strdup(will_message)}; - cc->session->lwt_msg.header.bits.qos = c->bits.will_qos; + c->session->lwt->header.bits.qos = packet->bits.will_qos; // We must store the retained message in the topic - if (c->bits.will_retain == 1) { - size_t publen = mqtt_size(&cc->session->lwt_msg, NULL); - unsigned char *payload = try_alloc(publen); - mqtt_pack(&cc->session->lwt_msg, payload); + if (packet->bits.will_retain == 1) { + size_t publen = mqtt_size(c->session->lwt, NULL); + // unsigned char *payload = try_alloc(publen); + unsigned char *payload = arena_alloc(&c->allocator, publen); + mqtt_write(c->session->lwt, payload); // We got a ready-to-be-sent bytestring in the retained message // field t->retained_msg = payload; } log_info("Will message specified (%lu bytes)", - cc->session->lwt_msg.publish.payloadlen); - log_info("\t%s", cc->session->lwt_msg.publish.payload); + c->session->lwt->publish.payloadlen); + log_info("\t%s", c->session->lwt->publish.payload); } // TODO check for session already present - cc->clean_session = c->bits.clean_session; + c->clean_session = packet->bits.clean_session; - set_connack(cc, MQTT_CONNECTION_ACCEPTED, session_present); + set_connack(c, MQTT_CONNECTION_ACCEPTED, session_present); - log_debug("Sending CONNACK to %s (%u, %u)", cc->client_id, session_present, + log_debug("Sending CONNACK to %s (%u, %u)", c->cid, session_present, MQTT_CONNECTION_ACCEPTED); return REPLY; -clientdc: +e_client_dc: return -ERRCLIENTDC; -bad_auth: - log_debug("Sending CONNACK to %s (%u, %u)", cc->client_id, session_present, - MQTT_BAD_USERNAME_OR_PASSWORD); - set_connack(cc, MQTT_BAD_USERNAME_OR_PASSWORD, session_present); +e_bad_auth: + log_debug("Sending CONNACK to %s (%u, %u)", c->cid, session_present, + MQTT_BAD_CREDENTIALS); + set_connack(c, MQTT_BAD_CREDENTIALS, session_present); - return MQTT_BAD_USERNAME_OR_PASSWORD; + return MQTT_BAD_CREDENTIALS; -not_authorized: - log_debug("Sending CONNACK to %s (%u, %u)", cc->client_id, session_present, +e_not_authorized: + log_debug("Sending CONNACK to %s (%u, %u)", c->cid, session_present, MQTT_NOT_AUTHORIZED); - set_connack(cc, MQTT_NOT_AUTHORIZED, session_present); + set_connack(c, MQTT_NOT_AUTHORIZED, session_present); return MQTT_NOT_AUTHORIZED; } -static int disconnect_handler(struct io_event *e) +static int disconnect_handler(Connection_Context *c) { - log_debug("Received DISCONNECT from %s", e->client->client_id); + log_debug("Received DISCONNECT from %s", c->cid); return -ERRCLIENTDC; } -static inline void add_wildcard(const char *topic, struct subscriber *s, - bool wildcard) +static inline void add_wildcard(const char *topic, Subscriber *s, bool wildcard) { - struct subscription *subscription = try_alloc(sizeof(*subscription)); - subscription->subscriber = s; - subscription->topic = try_strdup(topic); - subscription->multilevel = wildcard; - INCREF(s, struct subscriber); - topic_store_add_wildcard(server.store, subscription); + Subscription *subscription = try_alloc(sizeof(*subscription)); + subscription->subscriber = s; + subscription->topic = try_strdup(topic); + subscription->multilevel = wildcard; + topic_repo_add_wildcard(server.repo, subscription); } static void recursive_sub(struct trie_node *node, void *arg) { if (!node || !node->data) return; - struct topic *t = node->data; + Topic *topic = node->data; /* * We need to make a copy of the subscriber cause UTHASH needs a proper * handle to work correctly, otherwise we'll end up freeing the same * refernce on disconnect and break the table */ - struct subscriber *s = subscriber_clone(arg), *tmp; - HASH_FIND_STR(t->subscribers, s->id, tmp); + Subscriber *subscriber = subscriber_clone(arg), *tmp; + HASH_FIND_STR(topic->subscribers, subscriber->cid, tmp); if (!tmp) { - INCREF(s, struct subscriber); - HASH_ADD_STR(t->subscribers, id, s); + HASH_ADD_STR(topic->subscribers, cid, subscriber); } - log_debug("Adding subscriber %s to topic %s", s->session->session_id, - t->name); - list_push(s->session->subscriptions, t); + log_debug("Adding subscriber %s to topic %s", subscriber->cid, topic->name); + list_push(subscriber->session->subscriptions, topic); } -static int subscribe_handler(struct io_event *e) +static int subscribe_handler(Connection_Context *c) { - - bool wildcard = false; - struct mqtt_subscribe *s = &e->data.subscribe; + bool wildcard = false; + MQTT_Subscribe *subscribe = &c->data.subscribe; /* * We respond to the subscription request with SUBACK and a list of QoS in * the same exact order of reception */ - unsigned char rcs[s->tuples_len]; - struct client *c = e->client; + unsigned char rcs[subscribe->tuples_len]; /* Subscribe packets contains a list of topics and QoS tuples */ - for (unsigned i = 0; i < s->tuples_len; i++) { + for (unsigned i = 0; i < subscribe->tuples_len; i++) { - log_debug("Received SUBSCRIBE from %s", c->client_id); + log_debug("Received SUBSCRIBE from %s", c->cid); /* * Check if the topic exists already or in case create it and store in * the global map */ - char topic[s->tuples[i].topic_len + 2]; - snprintf(topic, s->tuples[i].topic_len + 1, "%s", s->tuples[i].topic); + char topic[subscribe->tuples[i].topic_len + 2]; + snprintf(topic, sizeof(topic), "%s", subscribe->tuples[i].topic); - log_debug("\t%s (QoS %i)", topic, s->tuples[i].qos); + log_debug("\t%s (QoS %i)", topic, subscribe->tuples[i].qos); /* Recursive subscribe to all children topics if the topic ends with * "/#" */ - if (topic[s->tuples[i].topic_len - 1] == '#' && - topic[s->tuples[i].topic_len - 2] == '/') { - topic[s->tuples[i].topic_len - 1] = '\0'; - wildcard = true; - } else if (topic[s->tuples[i].topic_len - 1] != '/') { - topic[s->tuples[i].topic_len] = '/'; - topic[s->tuples[i].topic_len + 1] = '\0'; + if (topic[subscribe->tuples[i].topic_len - 1] == '#' && + topic[subscribe->tuples[i].topic_len - 2] == '/') { + topic[subscribe->tuples[i].topic_len - 1] = '\0'; + wildcard = true; + } else if (topic[subscribe->tuples[i].topic_len - 1] != '/') { + topic[subscribe->tuples[i].topic_len] = '/'; + topic[subscribe->tuples[i].topic_len + 1] = '\0'; } - struct topic *t = topic_store_get_or_put(server.store, topic); + Topic *t = topic_repo_fetch_default(server.repo, topic); /* * Let's explore two possible scenarios: * 1. Normal topic (no single level wildcard '+') which can end with * multilevel wildcard '#' * 2. A topic contaning one or more single level wildcard '+' */ -#if THREADSNR > 0 - pthread_mutex_lock(&c->mutex); - pthread_mutex_lock(&mutex); -#endif if (!index(topic, '+')) { - struct subscriber *tmp; - HASH_FIND_STR(t->subscribers, c->client_id, tmp); + Subscriber *tmp; + HASH_FIND_STR(t->subscribers, c->cid, tmp); if (c->clean_session == true || !tmp) { if (!tmp) { - tmp = topic_add_subscriber(t, e->client->session, - s->tuples[i].qos); + tmp = topic_add_subscriber(t, c->session, + subscribe->tuples[i].qos); // we increment reference for the subscriptions session - INCREF(tmp, struct subscriber); } - list_push(e->client->session->subscriptions, t); + list_push(c->session->subscriptions, t); if (wildcard == true) { add_wildcard(topic, tmp, wildcard); - topic_store_map(server.store, topic, recursive_sub, tmp); + topic_repo_map(server.repo, topic, recursive_sub, tmp); } } } else { @@ -602,175 +524,133 @@ static int subscribe_handler(struct io_event *e) * the topic to the wildcards list as we can't know at this point * which topic it will match */ - struct subscriber *sub = - subscriber_new(e->client->session, s->tuples[i].qos); + Subscriber *sub = + subscriber_new(c->session, subscribe->tuples[i].qos); add_wildcard(topic, sub, wildcard); } -#if THREADSNR > 0 - pthread_mutex_unlock(&mutex); -#endif // Retained message? Publish it // TODO move after SUBACK response if (t->retained_msg) { size_t len = alloc_size(t->retained_msg); - memcpy(c->wbuf + c->towrite, t->retained_msg, len); - c->towrite += len; + memcpy(c->send_buf + c->write_total, t->retained_msg, len); + c->write_total += len; } -#if THREADSNR > 0 - pthread_mutex_unlock(&c->mutex); -#endif - rcs[i] = s->tuples[i].qos; + rcs[i] = subscribe->tuples[i].qos; } - struct mqtt_packet pkt = {.header = (union mqtt_header){.byte = SUBACK_B}}; - mqtt_suback(&pkt, s->pkt_id, rcs, s->tuples_len); - -#if THREADSNR > 0 - pthread_mutex_lock(&c->mutex); -#endif - size_t len = mqtt_size(&pkt, NULL); - mqtt_pack(&pkt, c->wbuf + c->towrite); - c->towrite += len; -#if THREADSNR > 0 - pthread_mutex_unlock(&c->mutex); -#endif + MQTT_Packet packet = {.header = (MQTT_Header){.byte = SUBACK_B}}; + mqtt_suback(&packet, subscribe->id, rcs, subscribe->tuples_len); - log_debug("Sending SUBACK to %s", c->client_id); + size_t len = mqtt_size(&packet, NULL); + mqtt_write(&packet, c->send_buf + c->write_total); + c->write_total += len; - mqtt_packet_destroy(&pkt); + log_debug("Sending SUBACK to %s", c->cid); return REPLY; } -static int unsubscribe_handler(struct io_event *e) +static int unsubscribe_handler(Connection_Context *c) { - - struct client *c = e->client; - - log_debug("Received UNSUBSCRIBE from %s", c->client_id); - -#if THREADSNR > 0 - pthread_mutex_lock(&c->mutex); - pthread_mutex_lock(&mutex); -#endif - struct topic *t = NULL; - for (int i = 0; i < e->data.unsubscribe.tuples_len; ++i) { - t = topic_store_get(server.store, - (const char *)e->data.unsubscribe.tuples[i].topic); - if (t) - topic_del_subscriber(t, c); + log_debug("Received UNSUBSCRIBE from %s", c->cid); + + Topic *topic = NULL; + for (int i = 0; i < c->data.unsubscribe.tuples_len; ++i) { + topic = topic_repo_fetch( + server.repo, (const char *)c->data.unsubscribe.tuples[i].topic); + if (topic) + topic_del_subscriber(topic, c); } -#if THREADSNR > 0 - pthread_mutex_unlock(&mutex); -#endif - - mqtt_pack_mono(c->wbuf + c->towrite, UNSUBACK, e->data.unsubscribe.pkt_id); - c->towrite += MQTT_ACK_LEN; -#if THREADSNR > 0 - pthread_mutex_unlock(&c->mutex); -#endif - - log_debug("Sending UNSUBACK to %s", c->client_id); + mqtt_write_ack(c->send_buf + c->write_total, UNSUBACK, + c->data.unsubscribe.id); + c->write_total += MQTT_ACK_LEN; - mqtt_packet_destroy(&e->data); + log_debug("Sending UNSUBACK to %s", c->cid); return REPLY; } -static int publish_handler(struct io_event *e) +static int publish_handler(Connection_Context *c) { - - struct client *c = e->client; - union mqtt_header *hdr = &e->data.header; - struct mqtt_publish *p = &e->data.publish; - unsigned short orig_mid = p->pkt_id; + MQTT_Header *header = &c->data.header; + MQTT_Publish *packet = &c->data.publish; + unsigned original_id = packet->id; log_debug( "Received PUBLISH from %s (d%i, q%u, r%i, m%u, %s, ... (%llu bytes))", - c->client_id, hdr->bits.dup, hdr->bits.qos, hdr->bits.retain, p->pkt_id, - p->topic, p->payloadlen); + c->cid, header->bits.dup, header->bits.qos, header->bits.retain, + packet->id, packet->topic, packet->payloadlen); info.messages_recv++; - char topic[p->topiclen + 2]; - unsigned char qos = hdr->bits.qos; + // TODO move to arena + char topic_name[packet->topiclen + 2]; + unsigned char qos = header->bits.qos; /* * For convenience we assure that all topics ends with a '/', indicating a * hierarchical level */ - if (p->topic[p->topiclen - 1] != '/') - snprintf(topic, p->topiclen + 2, "%s/", (const char *)p->topic); + if (packet->topic[packet->topiclen - 1] != '/') + snprintf(topic_name, sizeof(topic_name), "%s/", + (const char *)packet->topic); else - snprintf(topic, p->topiclen + 1, "%s", (const char *)p->topic); + snprintf(topic_name, sizeof(topic_name), "%s", + (const char *)packet->topic); -#if THREADSNR > 0 - pthread_mutex_lock(&c->mutex); - pthread_mutex_lock(&mutex); -#endif /* * Retrieve the topic from the global map, if it wasn't created before, * create a new one with the name selected */ - struct topic *t = topic_store_get_or_put(server.store, topic); + Topic *topic = topic_repo_fetch_default(server.repo, topic_name); /* Check for # wildcards subscriptions */ - if (topic_store_wildcards_empty(server.store)) { - topic_store_wildcards_foreach(item, server.store) + if (!topic_repo_wildcards_empty(server.repo)) { + topic_repo_wildcards_foreach(item, server.repo) { - struct subscription *s = item->data; - int matched = match_subscription(topic, s->topic, s->multilevel); + Subscription *subscription = item->data; + int matched = match_subscription(topic_name, subscription); if (matched == SOL_OK && - !is_subscribed(t, s->subscriber->session)) { + !is_subscribed(topic, subscription->subscriber->session)) { /* * We need to make a copy of the subscriber cause UTHASH needs * a proper handle to work correctly, otherwise we'll end up * freeing the same refernce on disconnect and break the table */ - struct subscriber *copy = subscriber_clone(s->subscriber); - INCREF(copy, struct subscriber); - HASH_ADD_STR(t->subscribers, id, copy); - list_push(s->subscriber->session->subscriptions, t); + Subscriber *copy = subscriber_clone(subscription->subscriber); + HASH_ADD_STR(topic->subscribers, cid, copy); + list_push(subscription->subscriber->session->subscriptions, + topic); } } } -#if THREADSNR > 0 - pthread_mutex_unlock(&mutex); -#endif - - struct mqtt_packet *pkt = mqtt_packet_alloc(e->data.header.byte); - // TODO must perform a deep copy here - pkt->publish = e->data.publish; - - if (hdr->bits.retain == 1) { - t->retained_msg = try_alloc(mqtt_size(&e->data, NULL)); - mqtt_pack(&e->data, t->retained_msg); + // MQTT_Packet *pkt = mqtt_packet_alloc(c->data.header.byte); + // // TODO must perform a deep copy here + // pkt->publish = c->data.publish; + + if (header->bits.retain == 1) { + topic->retained_msg = + arena_alloc(&c->allocator, mqtt_size(&c->data, NULL)); + mqtt_write(&c->data, topic->retained_msg); } -#if THREADSNR > 0 - pthread_mutex_unlock(&c->mutex); -#endif - if (publish_message(pkt, t) == 0) - DECREF(pkt, struct mqtt_packet); + publish_message(&c->data, topic, &c->allocator); + + // mqtt_packet_free(&c->data); // We have to answer to the publisher if (qos == AT_MOST_ONCE) goto exit; - int ptype = qos == EXACTLY_ONCE ? PUBREC : PUBACK; - -#if THREADSNR > 0 - pthread_mutex_lock(&c->mutex); -#endif - mqtt_ack(&e->data, ptype == PUBACK ? PUBACK_B : PUBREC_B); - mqtt_pack_mono(c->wbuf + c->towrite, ptype, orig_mid); - c->towrite += MQTT_ACK_LEN; -#if THREADSNR > 0 - pthread_mutex_unlock(&c->mutex); -#endif - log_debug("Sending %s to %s (m%u)", ptype == PUBACK ? "PUBACK" : "PUBREC", - c->client_id, orig_mid); + int ack_type = qos == EXACTLY_ONCE ? PUBREC : PUBACK; + packet->id = original_id; + + mqtt_ack(&c->data, packet->id); + mqtt_write_ack(c->send_buf + c->write_total, ack_type, packet->id); + c->write_total += MQTT_ACK_LEN; + log_debug("Sending %s to %s (m%u)", + ack_type == PUBACK ? "PUBACK" : "PUBREC", c->cid, packet->id); return REPLY; exit: @@ -782,91 +662,63 @@ static int publish_handler(struct io_event *e) return NOREPLY; } -static int puback_handler(struct io_event *e) +static int puback_handler(Connection_Context *c) { - struct client *c = e->client; - unsigned pkt_id = e->data.ack.pkt_id; - log_debug("Received PUBACK from %s (m%u)", c->client_id, pkt_id); -#if THREADSNR > 0 - pthread_mutex_lock(&c->mutex); -#endif - inflight_msg_clear(&c->session->i_msgs[pkt_id]); - c->session->i_msgs[pkt_id].packet = NULL; - c->session->i_acks[pkt_id] = -1; + unsigned packet_id = c->data.ack.id; + log_debug("Received PUBACK from %s (m%u)", c->cid, packet_id); + arena_free(&server.mqtt_allocator, + &c->session->i_msgs[packet_id].packet->publish.topic); + arena_free(&server.mqtt_allocator, + &c->session->i_msgs[packet_id].packet->publish.payload); + c->session->i_msgs[packet_id].packet = NULL; + c->session->i_msgs[packet_id].lastack_at = -1; --c->session->inflights; -#if THREADSNR > 0 - pthread_mutex_unlock(&c->mutex); -#endif return NOREPLY; } -static int pubrec_handler(struct io_event *e) +static int pubrec_handler(Connection_Context *c) { - struct client *c = e->client; - unsigned pkt_id = e->data.ack.pkt_id; - log_debug("Received PUBREC from %s (m%u)", c->client_id, pkt_id); -#if THREADSNR > 0 - pthread_mutex_lock(&c->mutex); -#endif - mqtt_pack_mono(c->wbuf + c->towrite, PUBREL, pkt_id); - c->towrite += MQTT_ACK_LEN; -#if THREADSNR > 0 - pthread_mutex_unlock(&c->mutex); -#endif + unsigned packet_id = c->data.ack.id; + log_debug("Received PUBREC from %s (m%u)", c->cid, packet_id); + c->write_total += + mqtt_write_ack(c->send_buf + c->write_total, PUBREL, packet_id); // Update inflight acks table - c->session->i_acks[pkt_id] = time(NULL); - log_debug("Sending PUBREL to %s (m%u)", c->client_id, pkt_id); + c->session->i_msgs[packet_id].lastack_at = time(NULL); + log_debug("Sending PUBREL to %s (m%u)", c->cid, packet_id); return REPLY; } -static int pubrel_handler(struct io_event *e) +static int pubrel_handler(Connection_Context *c) { - struct client *c = e->client; - unsigned pkt_id = e->data.ack.pkt_id; - log_debug("Received PUBREL from %s (m%u)", c->client_id, pkt_id); -#if THREADSNR > 0 - pthread_mutex_lock(&c->mutex); -#endif - mqtt_pack_mono(c->wbuf + c->towrite, PUBCOMP, pkt_id); - c->towrite += MQTT_ACK_LEN; -#if THREADSNR > 0 - pthread_mutex_unlock(&c->mutex); -#endif - log_debug("Sending PUBCOMP to %s (m%u)", c->client_id, pkt_id); + unsigned packet_id = c->data.ack.id; + log_debug("Received PUBREL from %s (m%u)", c->cid, packet_id); + c->write_total += + mqtt_write_ack(c->send_buf + c->write_total, PUBCOMP, packet_id); + log_debug("Sending PUBCOMP to %s (m%u)", c->cid, packet_id); return REPLY; } -static int pubcomp_handler(struct io_event *e) +static int pubcomp_handler(Connection_Context *c) { - struct client *c = e->client; - unsigned pkt_id = e->data.ack.pkt_id; - log_debug("Received PUBCOMP from %s (m%u)", c->client_id, pkt_id); -#if THREADSNR > 0 - pthread_mutex_lock(&c->mutex); -#endif - c->session->i_acks[pkt_id] = -1; - inflight_msg_clear(&c->session->i_msgs[pkt_id]); - c->session->i_msgs[pkt_id].packet = NULL; + unsigned packet_id = c->data.ack.id; + log_debug("Received PUBCOMP from %s (m%u)", c->cid, packet_id); + c->session->i_msgs[packet_id].lastack_at = -1; + arena_free(&server.mqtt_allocator, + &c->session->i_msgs[packet_id].packet->publish.payload); + arena_free(&server.mqtt_allocator, + &c->session->i_msgs[packet_id].packet->publish.topic); + c->session->i_msgs[packet_id].packet = NULL; --c->session->inflights; -#if THREADSNR > 0 - pthread_mutex_unlock(&c->mutex); -#endif return NOREPLY; } -static int pingreq_handler(struct io_event *e) +static int pingreq_handler(Connection_Context *c) { - log_debug("Received PINGREQ from %s", e->client->client_id); - e->data.header.byte = PINGRESP_B; -#if THREADSNR > 0 - pthread_mutex_lock(&e->client->mutex); -#endif - mqtt_pack(&e->data, e->client->wbuf + e->client->towrite); - e->client->towrite += MQTT_HEADER_LEN; -#if THREADSNR > 0 - pthread_mutex_unlock(&e->client->mutex); -#endif - log_debug("Sending PINGRESP to %s", e->client->client_id); + log_debug("Received PINGREQ from %s", c->cid); + c->data.header.byte = PINGRESP_B; + mqtt_write(&c->data, c->send_buf + c->write_total); + c->write_total += MQTT_HEADER_LEN; + log_debug("Sending PINGRESP to %s", c->cid); return REPLY; } @@ -874,7 +726,7 @@ static int pingreq_handler(struct io_event *e) * This is the only public API we expose from this module beside * publish_message. It just give access to handlers mapped by message type. */ -int handle_command(unsigned type, struct io_event *event) +int handle_command(Connection_Context *context) { - return handlers[type](event); + return handlers[context->data.header.bits.type](context); } diff --git a/src/iterator.c b/src/iterator.c index 6225760..a159feb 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -1,7 +1,7 @@ /* * BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -50,7 +50,7 @@ void iter_init(struct iterator *i, void *iterable, iter_next(i); } -void iter_destroy(struct iterator *i) { free_memory(i); } +void iter_free(struct iterator *i) { free_memory(i); } /* * Advance the iterator ptr to the next item in the iterable collection diff --git a/src/list.c b/src/list.c index 03aee67..d525d61 100644 --- a/src/list.c +++ b/src/list.c @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan + * Copyright (c) 2025, Andrea Giacomo Baldan * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,7 +49,7 @@ List *list_new(int (*destructor)(struct list_node *)) /* * Destroy a list, releasing all allocated memory */ -void list_destroy(List *l, int deep) +void list_free(List *l, int deep) { if (!l) return; diff --git a/src/logging.c b/src/logging.c index 27fea8c..7ff955f 100644 --- a/src/logging.c +++ b/src/logging.c @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/memory.c b/src/memory.c index e77f8ac..af0ed1f 100644 --- a/src/memory.c +++ b/src/memory.c @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,11 +26,10 @@ */ #include "memory.h" -#include #include #include -static atomic_size_t memory = ATOMIC_VAR_INIT(0); +static size_t memory = 0; /* * Custom malloc function, allocate a defined size of bytes plus diff --git a/src/memorypool.c b/src/memorypool.c deleted file mode 100644 index ae64748..0000000 --- a/src/memorypool.c +++ /dev/null @@ -1,136 +0,0 @@ -/* BSD 2-Clause License - * - * Copyright (c) 2023, Andrea Giacomo Baldan - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "memorypool.h" -#include "memory.h" -#include - -static void memorypool_resize(struct memorypool *); - -struct memorypool *memorypool_new(size_t blocks_nr, size_t blocksize) -{ - struct memorypool *pool = try_alloc(sizeof(*pool)); - blocksize = blocksize >= sizeof(intptr_t) ? blocksize : sizeof(intptr_t); - pool->memory = try_calloc(blocks_nr, blocksize); - pool->free = pool->memory; - pool->blocks_nr = blocks_nr; - pool->blocksize = blocksize; - if (!pool->free) { - free_memory(pool); - return NULL; - } - /* - * We pre-assign the position of each free block in the free pointer, this - * way we know every block position before allocating new memory, we'll - * call it the header of each block, where we store the offset in memory - * to reach the next free slot: - * - * ____________ - * _| 0x1ad45f02 | (0+blocksize) - * | |------------| - * | | . | - * | | . | - * |_|------------| - * _| 0x2ff43da1 | (1+blocksize) - * | |------------| - * | | . | - * | | . | - * |_|------------| - * | 0x98fff34a | (n+blocksize) - * |------------| - * | . | - * - * Just before assigning a free block of memory, we update the free pointer, - * pointing it to the memory address previously stored as r-value in it. - * This way everytime we allocate a new block we can refresh the next free - * block in the list. - */ - intptr_t *ptr = pool->free; - for (size_t i = 1; i != blocks_nr; ++i) { - *ptr = (intptr_t)i; - ptr = (intptr_t *)((char *)ptr + blocksize); - } - pool->block_used = 0; - return pool; -} - -void memorypool_destroy(struct memorypool *pool) -{ - free_memory(pool->memory); - free_memory(pool); -} - -void *memorypool_alloc(struct memorypool *pool) -{ - if (pool->block_used == pool->blocks_nr - 2) - memorypool_resize(pool); - void *ptr = pool->free; - /* - * After pointing the return pointer to the next free block, we need to - * update the next free block address on the free pointer. The address is - * already stored in the "header" of the block. - */ - pool->free = (intptr_t *)((char *)pool->memory + - (*((intptr_t *)pool->free)) * pool->blocksize); - pool->block_used++; - return ptr; -} - -void memorypool_free(struct memorypool *pool, void *ptr) -{ - /* - * Here we just need to point the header of the pointer to the next free - * location and udpate the current free location by pointing it to the - * free'd pointer - */ - *((intptr_t *)ptr) = *((intptr_t *)pool->free); - if (pool->block_used == pool->blocks_nr - 2) - memorypool_resize(pool); - pool->free = ptr; - pool->block_used--; -} - -static void memorypool_resize(struct memorypool *pool) -{ - pool->blocks_nr *= 2; - size_t newsize = pool->blocks_nr * pool->blocksize; - /* We extract next memory block offset position */ - intptr_t offset = *((intptr_t *)pool->free); - pool->memory = try_realloc(pool->memory, newsize); - pool->free = - (void *)((char *)pool->memory + ((offset - 1) * pool->blocksize)); - /* - * Apply the same logic of the init, but starting from the updated offset, - * the ald size of the pool - */ - intptr_t *ptr = (intptr_t *)pool->free; - for (size_t i = offset; i <= pool->blocks_nr; ++i) { - *ptr = (intptr_t)i; - ptr = (intptr_t *)((char *)ptr + pool->blocksize); - } -} diff --git a/src/memorypool.h b/src/memorypool.h deleted file mode 100644 index 1ce8fab..0000000 --- a/src/memorypool.h +++ /dev/null @@ -1,53 +0,0 @@ -/* BSD 2-Clause License - * - * Copyright (c) 2023, Andrea Giacomo Baldan - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef MEMORYPOOL_H -#define MEMORYPOOL_H - -#include - -/* - * Simple memory object-pool, the purpose is to allow for fixed size objects to - * be pre-allocated and re-use of memory blocks, so no size have to be - * specified like in a normal malloc but only alloc and free of a pointer is - * possible. - */ -struct memorypool { - void *memory; - void *free; - int block_used; - size_t blocks_nr; - size_t blocksize; -}; - -struct memorypool *memorypool_new(size_t, size_t); -void memorypool_destroy(struct memorypool *); -void *memorypool_alloc(struct memorypool *); -void memorypool_free(struct memorypool *, void *); - -#endif diff --git a/src/mqtt.c b/src/mqtt.c index b8c6c8d..7d7921b 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,34 +26,35 @@ */ #include "mqtt.h" +#include "arena.h" #include "memory.h" #include "pack.h" -#include "util.h" #include -typedef int mqtt_unpack_handler(u8 *, struct mqtt_packet *, usize); +typedef int mqtt_unpack_handler(u8 *, MQTT_Packet *, usize, Arena_Allocator *); -typedef usize mqtt_pack_handler(const struct mqtt_packet *, u8 *); +typedef usize mqtt_pack_handler(const MQTT_Packet *, u8 *); -static int unpack_mqtt_connect(u8 *, struct mqtt_packet *, usize); +static int unpack_mqtt_connect(u8 *, MQTT_Packet *, usize, Arena_Allocator *); -static int unpack_mqtt_publish(u8 *, struct mqtt_packet *, usize); +static int unpack_mqtt_publish(u8 *, MQTT_Packet *, usize, Arena_Allocator *); -static int unpack_mqtt_subscribe(u8 *, struct mqtt_packet *, usize); +static int unpack_mqtt_subscribe(u8 *, MQTT_Packet *, usize, Arena_Allocator *); -static int unpack_mqtt_unsubscribe(u8 *, struct mqtt_packet *, usize); +static int unpack_mqtt_unsubscribe(u8 *, MQTT_Packet *, usize, + Arena_Allocator *); -static int unpack_mqtt_ack(u8 *, struct mqtt_packet *, usize); +static int unpack_mqtt_ack(u8 *, MQTT_Packet *, usize, Arena_Allocator *); -static usize pack_mqtt_header(const union mqtt_header *, u8 *); +static usize pack_mqtt_header(const MQTT_Header *, u8 *); -static usize pack_mqtt_ack(const struct mqtt_packet *, u8 *); +static usize pack_mqtt_ack(const MQTT_Packet *, u8 *); -static usize pack_mqtt_connack(const struct mqtt_packet *, u8 *); +static usize pack_mqtt_connack(const MQTT_Packet *, u8 *); -static usize pack_mqtt_suback(const struct mqtt_packet *, u8 *); +static usize pack_mqtt_suback(const MQTT_Packet *, u8 *); -static usize pack_mqtt_publish(const struct mqtt_packet *, u8 *); +static usize pack_mqtt_publish(const MQTT_Packet *, u8 *); /* MQTT v3.1.1 standard */ static const int MAX_LEN_BYTES = 4; @@ -101,7 +102,7 @@ static mqtt_pack_handler *pack_handlers[13] = {NULL, * required to store itself. Refer to MQTT v3.1.1 algorithm for the * implementation. */ -int mqtt_encode_length(u8 *buf, usize len) +int mqtt_write_length(u8 *buf, usize len) { int bytes = 0; @@ -133,7 +134,7 @@ int mqtt_encode_length(u8 *buf, usize len) * * TODO Handle case where multiplier > 128 * 128 * 128 */ -usize mqtt_decode_length(u8 *buf, unsigned *pos) +usize mqtt_read_length(u8 *buf, unsigned *pos) { u8 c; @@ -217,7 +218,8 @@ usize mqtt_decode_length(u8 *buf, unsigned *pos) * unpack from the Variable Header position to the end of the packet as stated * by the total length expected. */ -static int unpack_mqtt_connect(u8 *buf, struct mqtt_packet *pkt, usize len) +static int unpack_mqtt_connect(u8 *buf, MQTT_Packet *packet, usize len, + Arena_Allocator *allocator) { /* @@ -232,29 +234,29 @@ static int unpack_mqtt_connect(u8 *buf, struct mqtt_packet *pkt, usize len) * Read variable header byte flags, followed by keepalive MSB and LSB * (2 bytes word) and the client ID length (2 bytes here again) */ - buf += unpack(buf, "BHH", &pkt->connect.byte, - &pkt->connect.payload.keepalive, &cid_len); + buf += read_struct(buf, "BHH", &packet->connect.byte, + &packet->connect.payload.keepalive, &cid_len); /* Read the client id */ if (cid_len > 0) { - memcpy(pkt->connect.payload.client_id, buf, cid_len); - pkt->connect.payload.client_id[cid_len] = '\0'; + memcpy(packet->connect.payload.client_id, buf, cid_len); + packet->connect.payload.client_id[cid_len] = '\0'; buf += cid_len; } /* Read the will topic and message if will is set on flags */ - if (pkt->connect.bits.will == 1) { - unpack_string16(&buf, &pkt->connect.payload.will_topic); - unpack_string16(&buf, &pkt->connect.payload.will_message); + if (packet->connect.bits.will == 1) { + read_string_u16(&buf, &packet->connect.payload.will_topic, allocator); + read_string_u16(&buf, &packet->connect.payload.will_message, allocator); } /* Read the username if username flag is set */ - if (pkt->connect.bits.username == 1) - unpack_string16(&buf, &pkt->connect.payload.username); + if (packet->connect.bits.username == 1) + read_string_u16(&buf, &packet->connect.payload.username, allocator); /* Read the password if password flag is set */ - if (pkt->connect.bits.password == 1) - unpack_string16(&buf, &pkt->connect.payload.password); + if (packet->connect.bits.password == 1) + read_string_u16(&buf, &packet->connect.payload.password, allocator); return MQTT_OK; } @@ -293,17 +295,19 @@ static int unpack_mqtt_connect(u8 *buf, struct mqtt_packet *pkt, usize len) * unpack from the Variable Header position to the end of the packet as stated * by the total length expected. */ -static int unpack_mqtt_publish(u8 *buf, struct mqtt_packet *pkt, usize len) +static int unpack_mqtt_publish(u8 *buf, MQTT_Packet *packet, usize len, + Arena_Allocator *allocator) { /* Read topic length and topic of the soon-to-be-published message */ - pkt->publish.topiclen = unpack_string16(&buf, &pkt->publish.topic); + packet->publish.topiclen = + read_string_u16(&buf, &packet->publish.topic, allocator); - if (!pkt->publish.topic) + if (!packet->publish.topic) return -MQTT_ERR; /* Read packet id */ - if (pkt->header.bits.qos > AT_MOST_ONCE) { - pkt->publish.pkt_id = unpack_integer(&buf, 'H'); + if (packet->header.bits.qos > AT_MOST_ONCE) { + packet->publish.id = read_int(&buf, 'H'); len -= sizeof(u16); } @@ -311,24 +315,25 @@ static int unpack_mqtt_publish(u8 *buf, struct mqtt_packet *pkt, usize len) * Message len is calculated subtracting the length of the variable header * from the Remaining Length field that is in the Fixed Header */ - len -= (sizeof(u16) + pkt->publish.topiclen); - pkt->publish.payloadlen = len; - pkt->publish.payload = unpack_bytes(&buf, len); + len -= (sizeof(u16) + packet->publish.topiclen); + packet->publish.payloadlen = len; + packet->publish.payload = read_bytes(&buf, len, allocator); - if (!pkt->publish.payload) + if (!packet->publish.payload) return -MQTT_ERR; return MQTT_OK; } -static int unpack_mqtt_subscribe(u8 *buf, struct mqtt_packet *pkt, usize len) +static int unpack_mqtt_subscribe(u8 *buf, MQTT_Packet *packet, usize len, + Arena_Allocator *allocator) { - struct mqtt_subscribe subscribe; + MQTT_Subscribe subscribe; subscribe.tuples = NULL; /* Read packet id */ - subscribe.pkt_id = unpack_integer(&buf, 'H'); + subscribe.id = read_int(&buf, 'H'); len -= sizeof(u16); /* @@ -347,18 +352,18 @@ static int unpack_mqtt_subscribe(u8 *buf, struct mqtt_packet *pkt, usize len) subscribe.tuples = try_realloc(subscribe.tuples, (i + 1) * sizeof(*subscribe.tuples)); subscribe.tuples[i].topic_len = - unpack_string16(&buf, &subscribe.tuples[i].topic); + read_string_u16(&buf, &subscribe.tuples[i].topic, allocator); if (!subscribe.tuples[i].topic) goto err; len -= subscribe.tuples[i].topic_len; - subscribe.tuples[i].qos = unpack_integer(&buf, 'B'); + subscribe.tuples[i].qos = read_int(&buf, 'B'); len -= sizeof(u8); } subscribe.tuples_len = i; - pkt->subscribe = subscribe; + packet->subscribe = subscribe; return MQTT_OK; @@ -366,14 +371,15 @@ static int unpack_mqtt_subscribe(u8 *buf, struct mqtt_packet *pkt, usize len) return -MQTT_ERR; } -static int unpack_mqtt_unsubscribe(u8 *buf, struct mqtt_packet *pkt, usize len) +static int unpack_mqtt_unsubscribe(u8 *buf, MQTT_Packet *packet, usize len, + Arena_Allocator *allocator) { - struct mqtt_unsubscribe unsubscribe; + MQTT_Unsubscribe unsubscribe; unsubscribe.tuples = NULL; /* Read packet id */ - unsubscribe.pkt_id = unpack_integer(&buf, 'H'); + unsubscribe.id = read_int(&buf, 'H'); len -= sizeof(u16); /* @@ -392,7 +398,7 @@ static int unpack_mqtt_unsubscribe(u8 *buf, struct mqtt_packet *pkt, usize len) unsubscribe.tuples = try_realloc(unsubscribe.tuples, (i + 1) * sizeof(*unsubscribe.tuples)); unsubscribe.tuples[i].topic_len = - unpack_string16(&buf, &unsubscribe.tuples[i].topic); + read_string_u16(&buf, &unsubscribe.tuples[i].topic, allocator); if (!unsubscribe.tuples[i].topic) goto err; @@ -401,7 +407,7 @@ static int unpack_mqtt_unsubscribe(u8 *buf, struct mqtt_packet *pkt, usize len) } unsubscribe.tuples_len = i; - pkt->unsubscribe = unsubscribe; + packet->unsubscribe = unsubscribe; return MQTT_OK; @@ -409,9 +415,10 @@ static int unpack_mqtt_unsubscribe(u8 *buf, struct mqtt_packet *pkt, usize len) return -MQTT_ERR; } -static int unpack_mqtt_ack(u8 *buf, struct mqtt_packet *pkt, usize len) +static int unpack_mqtt_ack(u8 *buf, MQTT_Packet *packet, usize len, + Arena_Allocator *allocator) { - pkt->ack = (struct mqtt_ack){.pkt_id = unpacku16(buf)}; + packet->ack = (MQTT_Ack){.id = read_u16(buf)}; return MQTT_OK; } @@ -419,19 +426,27 @@ static int unpack_mqtt_ack(u8 *buf, struct mqtt_packet *pkt, usize len) * Main unpacking function entry point. Call the correct unpacking function * through a dispatch table */ -int mqtt_unpack(u8 *buf, struct mqtt_packet *pkt, u8 byte, usize len) +int mqtt_read(u8 *buf, MQTT_Packet *packet, u8 byte, usize len, + Arena_Allocator *allocator) { - int rc = MQTT_OK; - u8 type = byte >> 4; + int rc = MQTT_OK; + u8 type = byte >> 4; - pkt->header = (union mqtt_header){.byte = byte}; + /* + * Check for OPCODE, if an unknown OPCODE is received return an + * error + */ + if (DISCONNECT < type || CONNECT > type) + return -MQTT_ERR; + + packet->header = (MQTT_Header){.byte = byte}; /* Call the appropriate unpack handler based on the message type */ if (type >= PINGREQ && type <= DISCONNECT) return rc; - rc = unpack_handlers[type](buf, pkt, len); + rc = unpack_handlers[type](buf, packet, len, allocator); return rc; } @@ -442,54 +457,54 @@ int mqtt_unpack(u8 *buf, struct mqtt_packet *pkt, u8 byte, usize len) * Meant to be called through a dispatch table, with command opcode as index */ -static usize pack_mqtt_header(const union mqtt_header *hdr, u8 *buf) +static usize pack_mqtt_header(const MQTT_Header *header, u8 *buf) { - pack(buf++, "B", hdr->byte); + write_struct(buf++, "B", header->byte); /* Encode 0 length bytes, message like this have only a fixed header */ - mqtt_encode_length(buf, 0); + mqtt_write_length(buf, 0); return MQTT_HEADER_LEN; } -static usize pack_mqtt_ack(const struct mqtt_packet *pkt, u8 *buf) +static usize pack_mqtt_ack(const MQTT_Packet *packet, u8 *buf) { - pack(buf, "BBH", pkt->header.byte, MQTT_HEADER_LEN, pkt->ack.pkt_id); + write_struct(buf, "BBH", packet->header.byte, MQTT_HEADER_LEN, + packet->ack.id); return MQTT_ACK_LEN; } -static usize pack_mqtt_connack(const struct mqtt_packet *pkt, u8 *buf) +static usize pack_mqtt_connack(const MQTT_Packet *packet, u8 *buf) { - pack(buf++, "B", pkt->header.byte); - buf += mqtt_encode_length(buf, MQTT_HEADER_LEN); + write_struct(buf++, "B", packet->header.byte); + buf += mqtt_write_length(buf, MQTT_HEADER_LEN); - pack(buf, "BB", pkt->connack.byte, pkt->connack.rc); + write_struct(buf, "BB", packet->connack.byte, packet->connack.rc); return MQTT_ACK_LEN; } -static usize pack_mqtt_suback(const struct mqtt_packet *pkt, u8 *buf) +static usize pack_mqtt_suback(const MQTT_Packet *packet, u8 *buf) { usize len = 0; - usize pktlen = mqtt_size(pkt, &len); + usize pktlen = mqtt_size(packet, &len); - pack(buf++, "B", pkt->header.byte); - buf += mqtt_encode_length(buf, len); + write_struct(buf++, "B", packet->header.byte); + buf += mqtt_write_length(buf, len); - buf += pack(buf, "H", pkt->suback.pkt_id); - for (int i = 0; i < pkt->suback.rcslen; i++) - pack(buf++, "B", pkt->suback.rcs[i]); + buf += write_struct(buf, "H", packet->suback.id); + for (int i = 0; i < packet->suback.rcslen; i++) + write_struct(buf++, "B", packet->suback.rcs[i]); return pktlen; } -static usize pack_mqtt_publish(const struct mqtt_packet *pkt, u8 *buf) +static usize pack_mqtt_publish(const MQTT_Packet *packet, u8 *buf) { - /* * We must calculate the total length of the packet including header and * length field of the fixed header part @@ -497,27 +512,27 @@ static usize pack_mqtt_publish(const struct mqtt_packet *pkt, u8 *buf) // Total len of the packet excluding fixed header len usize len = 0L; - usize pktlen = mqtt_size(pkt, &len); + usize pktlen = mqtt_size(packet, &len); - pack(buf++, "B", pkt->header.byte); + write_struct(buf++, "B", packet->header.byte); /* * TODO handle case where step is > 1, e.g. when a message longer than 128 * bytes is published */ - buf += mqtt_encode_length(buf, len); + buf += mqtt_write_length(buf, len); // Topic len followed by topic name in bytes - buf += pack(buf, "H", pkt->publish.topiclen); - memcpy(buf, pkt->publish.topic, pkt->publish.topiclen); - buf += pkt->publish.topiclen; + buf += write_struct(buf, "H", packet->publish.topiclen); + memcpy(buf, packet->publish.topic, packet->publish.topiclen); + buf += packet->publish.topiclen; // Packet id - if (pkt->header.bits.qos > AT_MOST_ONCE) - buf += pack(buf, "H", pkt->publish.pkt_id); + if (packet->header.bits.qos > AT_MOST_ONCE) + buf += write_struct(buf, "H", packet->publish.id); // Finally the payload, same way of topic, payload len -> payload - memcpy(buf, pkt->publish.payload, pkt->publish.payloadlen); + memcpy(buf, packet->publish.payload, packet->publish.payloadlen); return pktlen; } @@ -526,85 +541,85 @@ static usize pack_mqtt_publish(const struct mqtt_packet *pkt, u8 *buf) * Main packing function entry point. Call the correct packing function through * a dispatch table */ -usize mqtt_pack(const struct mqtt_packet *pkt, u8 *buf) +usize mqtt_write(const MQTT_Packet *packet, u8 *buf) { - u8 type = pkt->header.bits.type; + u8 type = packet->header.bits.type; if (type == PINGREQ || type == PINGRESP) - return pack_mqtt_header(&pkt->header, buf); - return pack_handlers[type](pkt, buf); + return pack_mqtt_header(&packet->header, buf); + return pack_handlers[type](packet, buf); } /* * MQTT packets building functions */ -void mqtt_ack(struct mqtt_packet *pkt, u16 pkt_id) +void mqtt_ack(MQTT_Packet *packet, u16 packet_id) { - pkt->ack = (struct mqtt_ack){.pkt_id = pkt_id}; + packet->ack = (MQTT_Ack){.id = packet_id}; } -void mqtt_connack(struct mqtt_packet *pkt, u8 cflags, u8 rc) +void mqtt_connack(MQTT_Packet *packet, u8 cflags, u8 rc) { - pkt->connack = (struct mqtt_connack){.byte = cflags, .rc = rc}; + packet->connack = (MQTT_Connack){.byte = cflags, .rc = rc}; } -void mqtt_suback(struct mqtt_packet *pkt, u16 pkt_id, u8 *rcs, u16 rcslen) +void mqtt_suback(MQTT_Packet *packet, u16 packet_id, u8 *rcs, u16 rcslen) { - pkt->suback = (struct mqtt_suback){ - .pkt_id = pkt_id, .rcslen = rcslen, .rcs = try_alloc(rcslen)}; - memcpy(pkt->suback.rcs, rcs, rcslen); + packet->suback = (MQTT_Suback){ + .id = packet_id, .rcslen = rcslen, .rcs = try_alloc(rcslen)}; + memcpy(packet->suback.rcs, rcs, rcslen); } -void mqtt_packet_publish(struct mqtt_packet *pkt, u16 pkt_id, usize topiclen, +void mqtt_packet_publish(MQTT_Packet *packet, u16 packet_id, usize topiclen, u8 *topic, usize payloadlen, u8 *payload) { - pkt->publish = (struct mqtt_publish){.pkt_id = pkt_id, - .topiclen = topiclen, - .topic = topic, - .payloadlen = payloadlen, - .payload = payload}; + packet->publish = (MQTT_Publish){.id = packet_id, + .topiclen = topiclen, + .topic = topic, + .payloadlen = payloadlen, + .payload = payload}; } -void mqtt_packet_destroy(struct mqtt_packet *pkt) +void mqtt_packet_free(MQTT_Packet *packet) { - switch (pkt->header.bits.type) { + switch (packet->header.bits.type) { case CONNECT: - if (pkt->connect.bits.username == 1) - free_memory(pkt->connect.payload.username); - if (pkt->connect.bits.password == 1) - free_memory(pkt->connect.payload.password); - if (pkt->connect.bits.will == 1) { - free_memory(pkt->connect.payload.will_message); - free_memory(pkt->connect.payload.will_topic); + if (packet->connect.bits.username == 1) + free_memory(packet->connect.payload.username); + if (packet->connect.bits.password == 1) + free_memory(packet->connect.payload.password); + if (packet->connect.bits.will == 1) { + free_memory(packet->connect.payload.will_message); + free_memory(packet->connect.payload.will_topic); } break; case SUBSCRIBE: case UNSUBSCRIBE: - for (unsigned i = 0; i < pkt->subscribe.tuples_len; i++) - free_memory(pkt->subscribe.tuples[i].topic); - free_memory(pkt->subscribe.tuples); + for (unsigned i = 0; i < packet->subscribe.tuples_len; i++) + free_memory(packet->subscribe.tuples[i].topic); + free_memory(packet->subscribe.tuples); break; case SUBACK: - free_memory(pkt->suback.rcs); + free_memory(packet->suback.rcs); break; case PUBLISH: - free_memory(pkt->publish.topic); - free_memory(pkt->publish.payload); + free_memory(packet->publish.topic); + free_memory(packet->publish.payload); break; default: break; } } -void mqtt_set_dup(struct mqtt_packet *pkt) { pkt->header.bits.dup = 1; } +void mqtt_set_dup(MQTT_Packet *packet) { packet->header.bits.dup = 1; } /* * Helper function for ACKs with a packet identifier, just encode a bytearray * of length 4, 1 byte for the fixed header, 1 for the encoded length of the * packet and 2 for the packet identifier value, which is a 16 bit integer */ -int mqtt_pack_mono(u8 *buf, u8 op, u16 id) +int mqtt_write_ack(u8 *buf, u8 op, u16 id) { u8 byte = 0; switch (op) { @@ -624,9 +639,9 @@ int mqtt_pack_mono(u8 *buf, u8 op, u16 id) byte = UNSUBACK_B; break; } - pack(buf++, "B", byte); - buf += mqtt_encode_length(buf, MQTT_HEADER_LEN); - pack(buf, "H", id); + write_struct(buf++, "B", byte); + buf += mqtt_write_length(buf, MQTT_HEADER_LEN); + write_struct(buf, "H", id); return 4; // u8=1 + u16=2 + 1 byte for remaining bytes field } @@ -637,18 +652,18 @@ int mqtt_pack_mono(u8 *buf, u8 op, u16 id) * excluding the fixed header (1 byte) and the bytes needed to store the * value itself (1 to 3 bytes). */ -usize mqtt_size(const struct mqtt_packet *pkt, usize *len) +usize mqtt_size(const MQTT_Packet *packet, usize *len) { usize size = 0LL; - switch (pkt->header.bits.type) { + switch (packet->header.bits.type) { case PUBLISH: - size = MQTT_HEADER_LEN + sizeof(uint16_t) + pkt->publish.topiclen + - pkt->publish.payloadlen; - if (pkt->header.bits.qos > AT_MOST_ONCE) + size = MQTT_HEADER_LEN + sizeof(uint16_t) + packet->publish.topiclen + + packet->publish.payloadlen; + if (packet->header.bits.qos > AT_MOST_ONCE) size += sizeof(uint16_t); break; case SUBACK: - size = MQTT_HEADER_LEN + sizeof(uint16_t) + pkt->suback.rcslen; + size = MQTT_HEADER_LEN + sizeof(uint16_t) + packet->suback.rcslen; break; default: size = MQTT_ACK_LEN; @@ -673,20 +688,10 @@ usize mqtt_size(const struct mqtt_packet *pkt, usize *len) return size; } -static void mqtt_packet_free(const struct ref *refcount) -{ - struct mqtt_packet *pkt = - container_of(refcount, struct mqtt_packet, refcount); - mqtt_packet_destroy(pkt); - free_memory(pkt); -} - /* Just a packet allocing with the reference counter set */ -struct mqtt_packet *mqtt_packet_alloc(u8 byte) +MQTT_Packet *mqtt_packet_alloc(u8 byte, Pool_Allocator *allocator) { - struct mqtt_packet *packet = try_alloc(sizeof(*packet)); - packet->header = (union mqtt_header){.byte = byte}; - packet->refcount = (struct ref){mqtt_packet_free, 0}; - packet->refcount.count = ATOMIC_VAR_INIT(0); + MQTT_Packet *packet = pool_alloc(allocator); + packet->header = (MQTT_Header){.byte = byte}; return packet; } diff --git a/src/network.c b/src/network.c index 7c9c4ef..817431b 100644 --- a/src/network.c +++ b/src/network.c @@ -1,7 +1,7 @@ /* * BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -264,21 +264,21 @@ ssize_t send_bytes(int fd, const unsigned char *buf, size_t len) */ ssize_t recv_bytes(int fd, unsigned char *buf, size_t bufsize) { - ssize_t n = 0; ssize_t total = 0; - while (total < (ssize_t)bufsize) { - + while (total < bufsize) { if ((n = recv(fd, buf, bufsize - total, 0)) < 0) { + total = total == 0 ? -1 : total; if (errno == EAGAIN || errno == EWOULDBLOCK) break; else goto err; } - if (n == 0) - return 0; + if (n == 0) { + return total; + } buf += n; total += n; diff --git a/src/pack.c b/src/pack.c index eba902d..298895f 100644 --- a/src/pack.c +++ b/src/pack.c @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023 Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025 Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,6 +26,7 @@ */ #include "pack.h" +#include "arena.h" #include "memory.h" #include #include @@ -34,18 +35,18 @@ // Beej'us network guide functions /* -** packi16() -- store a 16-bit int into a char buffer (like htons()) +** write_i16() -- store a 16-bit int into a char buffer (like htons()) */ -void packi16(u8 *buf, u16 val) +void write_i16(u8 *buf, u16 val) { *buf++ = val >> 8; *buf++ = val; } /* -** packi32() -- store a 32-bit int into a char buffer (like htonl()) +** write_i32() -- store a 32-bit int into a char buffer (like htonl()) */ -void packi32(u8 *buf, u32 val) +void write_i32(u8 *buf, u32 val) { *buf++ = val >> 24; *buf++ = val >> 16; @@ -54,9 +55,9 @@ void packi32(u8 *buf, u32 val) } /* -** packi64() -- store a 64-bit int into a char buffer (like htonl()) +** write_i64() -- store a 64-bit int into a char buffer (like htonl()) */ -void packi64(u8 *buf, u64 val) +void write_i64(u8 *buf, u64 val) { *buf++ = val >> 56; *buf++ = val >> 48; @@ -69,9 +70,9 @@ void packi64(u8 *buf, u64 val) } /* -** unpacki16() -- unpack a 16-bit int from a char buffer (like ntohs()) +** read_i16() -- unpack a 16-bit int from a char buffer (like ntohs()) */ -i16 unpacki16(u8 *buf) +i16 read_i16(const u8 *buf) { u16 i2 = ((u16)buf[0] << 8) | buf[1]; i16 val; @@ -86,14 +87,14 @@ i16 unpacki16(u8 *buf) } /* -** unpacku16() -- unpack a 16-bit unsigned from a char buffer (like ntohs()) +** read_u16() -- unpack a 16-bit unsigned from a char buffer (like ntohs()) */ -u16 unpacku16(u8 *buf) { return ((u16)buf[0] << 8) | buf[1]; } +u16 read_u16(const u8 *buf) { return ((u16)buf[0] << 8) | buf[1]; } /* -** unpacki32() -- unpack a 32-bit int from a char buffer (like ntohl()) +** read_i32() -- unpack a 32-bit int from a char buffer (like ntohl()) */ -i32 unpacki32(u8 *buf) +i32 read_i32(const u8 *buf) { u32 i2 = ((i32)buf[0] << 24) | ((i32)buf[1] << 16) | ((i32)buf[2] << 8) | buf[3]; @@ -109,18 +110,18 @@ i32 unpacki32(u8 *buf) } /* -** unpacku32() -- unpack a 32-bit unsigned from a char buffer (like ntohl()) +** read_u32() -- unpack a 32-bit unsigned from a char buffer (like ntohl()) */ -u32 unpacku32(u8 *buf) +u32 read_u32(const u8 *buf) { return ((u32)buf[0] << 24) | ((u32)buf[1] << 16) | ((u32)buf[2] << 8) | buf[3]; } /* -** unpacki64() -- unpack a 64-bit int from a char buffer (like ntohl()) +** read_i64() -- unpack a 64-bit int from a char buffer (like ntohl()) */ -i64 unpacki64(u8 *buf) +i64 read_i64(const u8 *buf) { u64 i2 = ((u64)buf[0] << 56) | ((u64)buf[1] << 48) | ((u64)buf[2] << 40) | ((u64)buf[3] << 32) | ((u64)buf[4] << 24) | ((u64)buf[5] << 16) | @@ -137,9 +138,9 @@ i64 unpacki64(u8 *buf) } /* -** unpacku64() -- unpack a 64-bit unsigned from a char buffer (like ntohl()) +** read_u64() -- unpack a 64-bit unsigned from a char buffer (like ntohl()) */ -u64 unpacku64(u8 *buf) +u64 read_u64(const u8 *buf) { return ((u64)buf[0] << 56) | ((u64)buf[1] << 48) | ((u64)buf[2] << 40) | ((u64)buf[3] << 32) | ((u64)buf[4] << 24) | ((u64)buf[5] << 16) | @@ -147,7 +148,7 @@ u64 unpacku64(u8 *buf) } /* - * pack() -- store data dictated by the format string in the buffer + * write_struct() -- store data dictated by the format string in the buffer * * bits |signed unsigned float string * -----+---------------------------------- @@ -159,7 +160,7 @@ u64 unpacku64(u8 *buf) * * (16-bit unsigned length is automatically prepended to strings) */ -usize pack(u8 *buf, char *format, ...) +usize write_struct(u8 *buf, char *format, ...) { va_list ap; @@ -197,42 +198,42 @@ usize pack(u8 *buf, char *format, ...) case 'h': // 16-bit size += 2; h = va_arg(ap, i32); - packi16(buf, h); + write_i16(buf, h); buf += 2; break; case 'H': // 16-bit unsigned size += 2; H = va_arg(ap, u32); - packi16(buf, H); + write_i16(buf, H); buf += 2; break; case 'i': // 32-bit size += 4; i = va_arg(ap, i32); - packi32(buf, i); + write_i32(buf, i); buf += 4; break; case 'I': // 32-bit unsigned size += 4; I = va_arg(ap, u32); - packi32(buf, I); + write_i32(buf, I); buf += 4; break; case 'q': // 64-bit size += 8; q = va_arg(ap, i64); - packi64(buf, q); + write_i64(buf, q); buf += 8; break; case 'Q': // 64-bit unsigned size += 8; Q = va_arg(ap, u64); - packi64(buf, Q); + write_i64(buf, Q); buf += 8; break; @@ -251,7 +252,7 @@ usize pack(u8 *buf, char *format, ...) } /* - * unpack() -- unpack data dictated by the format string into the buffer + * read_struct() -- unpack data dictated by the format string into the buffer * * bits |signed unsigned float string * -----+---------------------------------- @@ -264,7 +265,7 @@ usize pack(u8 *buf, char *format, ...) * (string is extracted based on its stored length, but 's' can be * prepended with a max length) */ -usize unpack(u8 *buf, char *format, ...) +usize read_struct(u8 *buf, char *format, ...) { va_list ap; @@ -305,42 +306,42 @@ usize unpack(u8 *buf, char *format, ...) case 'h': // 16-bit h = va_arg(ap, i16 *); - *h = unpacki16(buf); + *h = read_i16(buf); buf += 2; size += 2; break; case 'H': // 16-bit unsigned H = va_arg(ap, u16 *); - *H = unpacku16(buf); + *H = read_u16(buf); buf += 2; size += 2; break; case 'i': // 32-bit i = va_arg(ap, i32 *); - *i = unpacki32(buf); + *i = read_i32(buf); buf += 4; size += 4; break; case 'I': // 32-bit unsigned I = va_arg(ap, u32 *); - *I = unpacku32(buf); + *I = read_u32(buf); buf += 4; size += 4; break; case 'q': // 64-bit q = va_arg(ap, i64 *); - *q = unpacki64(buf); + *q = read_i64(buf); buf += 8; size += 8; break; case 'Q': // 64-bit unsigned Q = va_arg(ap, u64 *); - *Q = unpacku64(buf); + *Q = read_u64(buf); buf += 8; size += 8; break; @@ -368,7 +369,7 @@ usize unpack(u8 *buf, char *format, ...) } /* Helper functions */ -i64 unpack_integer(u8 **buf, i8 size) +i64 read_int(u8 **buf, i8 size) { i64 val = 0LL; switch (size) { @@ -381,45 +382,46 @@ i64 unpack_integer(u8 **buf, i8 size) *buf += 1; break; case 'h': - val = unpacki16(*buf); + val = read_i16(*buf); *buf += 2; break; case 'H': - val = unpacku16(*buf); + val = read_u16(*buf); *buf += 2; break; case 'i': - val = unpacki32(*buf); + val = read_i32(*buf); *buf += 4; break; case 'I': - val = unpacku32(*buf); + val = read_u32(*buf); *buf += 4; break; case 'q': - val = unpacki64(*buf); + val = read_i64(*buf); *buf += 8; break; case 'Q': - val = unpacku64(*buf); + val = read_u64(*buf); *buf += 8; break; } return val; } -u8 *unpack_bytes(u8 **buf, usize len) +u8 *read_bytes(u8 **buf, usize len, Arena_Allocator *allocator) { - u8 *dest = try_alloc(len + 1); + // u8 *dest = try_alloc(len + 1); + u8 *dest = arena_alloc(allocator, len + 1); memcpy(dest, *buf, len); dest[len] = '\0'; *buf += len; return dest; } -u16 unpack_string16(u8 **buf, u8 **dest) +u16 read_string_u16(u8 **buf, u8 **dest, Arena_Allocator *allocator) { - u16 len = unpack_integer(buf, 'H'); - *dest = unpack_bytes(buf, len); + u16 len = read_int(buf, 'H'); + *dest = read_bytes(buf, len, allocator); return len; } diff --git a/src/server.c b/src/server.c index 5684d65..c8c6233 100644 --- a/src/server.c +++ b/src/server.c @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,12 +26,12 @@ */ #include "server.h" +#include "arena.h" #include "config.h" #include "ev.h" #include "handlers.h" #include "logging.h" #include "memory.h" -#include "memorypool.h" #include "network.h" #include "sol_internal.h" #include @@ -39,7 +39,8 @@ #include #include -pthread_mutex_t mutex; +// static Pool_Allocator general_allocator; +static Arena_Allocator general_allocator; /* * Auxiliary structure to be used as init argument for eventloop, fd is the @@ -49,7 +50,7 @@ pthread_mutex_t mutex; */ struct listen_payload { int fd; - atomic_bool cronjobs; + bool cronjobs; }; /* Seconds in a Sol, easter egg */ @@ -113,31 +114,33 @@ struct server server; * for each different thread. */ -static void client_init(struct client *); +static void connection_ctx_init(Connection_Context *); -static void client_deactivate(struct client *); +static void connection_ctx_deinit(Connection_Context *); // CALLBACKS for the eventloop static void accept_callback(struct ev_ctx *, void *); -static void read_callback(struct ev_ctx *, void *); +static void recv_callback(struct ev_ctx *, void *); -static void write_callback(struct ev_ctx *, void *); +static void send_callback(struct ev_ctx *, void *); /* * Processing message function, will be applied on fully formed mqtt packet * received on read_callback callback */ -static void process_message(struct ev_ctx *, struct client *); +// static void process_message(struct ev_ctx *, struct client *); /* Periodic routine to publish general stats about the broker on $SOL topics */ static void publish_stats(struct ev_ctx *, void *); +static void sweep_completed_inflights(struct ev_ctx *, void *); + /* * Periodic routine to check for incomplete transactions on QoS > 0 to be * concluded */ -static void inflight_msg_check(struct ev_ctx *, void *); +static void inflight_message_check(struct ev_ctx *, void *); /* * Statistics topics, published every N seconds defined by configuration @@ -190,7 +193,7 @@ static const char *solerr(int rc) return "[MQTT] Wrong identifier"; case MQTT_SERVER_UNAVAILABLE: return "[MQTT] Server unavailable"; - case MQTT_BAD_USERNAME_OR_PASSWORD: + case MQTT_BAD_CREDENTIALS: return "[MQTT] Bad username or password"; case MQTT_NOT_AUTHORIZED: return "[MQTT] Not authorized"; @@ -207,7 +210,7 @@ static const char *solerr(int rc) /* * Publish statistics periodic task, it will be called once every N config - * defined seconds, it publishes some informations on predefined topics + * defined seconds, it publishes some information on predefined topics */ static void publish_stats(struct ev_ctx *ctx, void *data) { @@ -238,15 +241,16 @@ static void publish_stats(struct ev_ctx *ctx, void *data) snprintf(mem, 21, "%lld", memory); // $SOL/uptime - struct mqtt_packet p = {.header = (union mqtt_header){.byte = PUBLISH_B}, - .publish = (struct mqtt_publish){ - .pkt_id = 0, - .topiclen = sys_topics[2].len, - .topic = (unsigned char *)sys_topics[2].name, - .payloadlen = strlen(utime), - .payload = (unsigned char *)&utime}}; + MQTT_Packet p = { + .header = (MQTT_Header){.byte = PUBLISH_B}, + .publish = (MQTT_Publish){.id = 0, + .topiclen = sys_topics[2].len, + .topic = (unsigned char *)sys_topics[2].name, + .payloadlen = strlen(utime), + .payload = (unsigned char *)&utime}}; - publish_message(&p, topic_store_get(server.store, sys_topics[2].name)); + publish_message(&p, topic_repo_fetch(server.repo, sys_topics[2].name), + &general_allocator); // $SOL/broker/uptime/sol p.publish.topiclen = sys_topics[3].len; @@ -254,7 +258,8 @@ static void publish_stats(struct ev_ctx *ctx, void *data) p.publish.payloadlen = strlen(sutime); p.publish.payload = (unsigned char *)&sutime; - publish_message(&p, topic_store_get(server.store, sys_topics[3].name)); + publish_message(&p, topic_repo_fetch(server.repo, sys_topics[3].name), + &general_allocator); // $SOL/broker/clients/connected p.publish.topiclen = sys_topics[4].len; @@ -262,7 +267,8 @@ static void publish_stats(struct ev_ctx *ctx, void *data) p.publish.payloadlen = strlen(cclients); p.publish.payload = (unsigned char *)&cclients; - publish_message(&p, topic_store_get(server.store, sys_topics[4].name)); + publish_message(&p, topic_repo_fetch(server.repo, sys_topics[4].name), + &general_allocator); // $SOL/broker/bytes/sent p.publish.topiclen = sys_topics[6].len; @@ -270,7 +276,8 @@ static void publish_stats(struct ev_ctx *ctx, void *data) p.publish.payloadlen = strlen(bsent); p.publish.payload = (unsigned char *)&bsent; - publish_message(&p, topic_store_get(server.store, sys_topics[6].name)); + publish_message(&p, topic_repo_fetch(server.repo, sys_topics[6].name), + &general_allocator); // $SOL/broker/messages/sent p.publish.topiclen = sys_topics[8].len; @@ -278,7 +285,8 @@ static void publish_stats(struct ev_ctx *ctx, void *data) p.publish.payloadlen = strlen(msent); p.publish.payload = (unsigned char *)&msent; - publish_message(&p, topic_store_get(server.store, sys_topics[8].name)); + publish_message(&p, topic_repo_fetch(server.repo, sys_topics[8].name), + &general_allocator); // $SOL/broker/messages/received p.publish.topiclen = sys_topics[9].len; @@ -286,7 +294,8 @@ static void publish_stats(struct ev_ctx *ctx, void *data) p.publish.payloadlen = strlen(mrecv); p.publish.payload = (unsigned char *)&mrecv; - publish_message(&p, topic_store_get(server.store, sys_topics[9].name)); + publish_message(&p, topic_repo_fetch(server.repo, sys_topics[9].name), + &general_allocator); // $SOL/broker/memory/used p.publish.topiclen = sys_topics[10].len; @@ -294,7 +303,8 @@ static void publish_stats(struct ev_ctx *ctx, void *data) p.publish.payloadlen = strlen(mem); p.publish.payload = (unsigned char *)&mem; - publish_message(&p, topic_store_get(server.store, sys_topics[10].name)); + publish_message(&p, topic_repo_fetch(server.repo, sys_topics[10].name), + &general_allocator); } /* @@ -305,66 +315,73 @@ static void publish_stats(struct ev_ctx *ctx, void *data) * unserialized, this way it's possible to set the DUP flag easily at the cost * of additional packing before re-sending it out */ -static void inflight_msg_check(struct ev_ctx *ctx, void *data) +static void inflight_message_check(struct ev_ctx *ctx, void *data) { (void)data; (void)ctx; - size_t size = 0; - time_t now = time(NULL); - struct mqtt_packet *p = NULL; - struct client *c, *tmp; -#if THREADSNR > 0 - pthread_mutex_lock(&mutex); -#endif - HASH_ITER(hh, server.clients_map, c, tmp) + size_t size = 0; + time_t now = time(NULL); + MQTT_Packet *p = NULL; + Connection_Context *c, *tmp; + HASH_ITER(hh, server.contexts, c, tmp) { if (!c || !c->connected || !c->session || !has_inflight(c->session)) continue; -#if THREADSNR > 0 - pthread_mutex_lock(&c->mutex); -#endif for (int i = 1; i < MAX_INFLIGHT_MSGS; ++i) { + if (!c->session->i_msgs[i].packet) + continue; // TODO remove 20 hardcoded value // Messages - if (c->session->i_msgs[i].packet && - (now - c->session->i_msgs[i].seen) > 20) { - log_debug("Re-sending message to %s", c->client_id); - p = c->session->i_msgs[i].packet; - p->header.bits.qos = c->session->i_msgs[i].qos; + if ((now - c->session->i_msgs[i].lastack_at) > 20) { + log_debug("Re-sending message to %s", c->cid); + p = c->session->i_msgs[i].packet; + // p->header.bits.qos = c->session->i_msgs[i].qos; // Set DUP flag to 1 mqtt_set_dup(p); size = mqtt_size(c->session->i_msgs[i].packet, NULL); // Serialize the packet and send it out again - mqtt_pack(p, c->wbuf + c->towrite); - c->towrite += size; - enqueue_event_write(c); - // Update information stats - info.messages_sent++; - } - // ACKs - if (c->session->i_acks[i] > 0 && - (now - c->session->i_acks[i]) > 20) { - log_debug("Re-sending ack to %s", c->client_id); - struct mqtt_packet ack; + mqtt_write(p, c->send_buf + c->write_total); + c->write_total += size; + } else if (now - c->session->i_msgs[i].lastack_at > 20) { + // ACKs + log_debug("Re-sending ack to %s", c->cid); + // TODO track the last ACK type sent to determine which one to + // send + MQTT_Packet ack; mqtt_ack(&ack, i); // Set DUP flag to 1 mqtt_set_dup(&ack); size = mqtt_size(&ack, NULL); // Serialize the packet and send it out again - mqtt_pack(p, c->wbuf + c->towrite); - c->towrite += size; - enqueue_event_write(c); - // Update information stats - info.messages_sent++; + mqtt_write(p, c->send_buf + c->write_total); + c->write_total += size; } + // enqueue_event_write(c); + // Update information stats + info.messages_sent++; + } + } +} + +static void sweep_completed_inflights(struct ev_ctx *ctx, void *data) +{ + (void)data; + (void)ctx; + Connection_Context *c, *tmp; + HASH_ITER(hh, server.contexts, c, tmp) + { + if (!c || !c->connected || !c->session || !has_inflight(c->session)) + continue; + for (int i = 1; i < MAX_INFLIGHT_MSGS; ++i) { + if (!c->session->i_msgs[i].packet || + c->session->i_msgs[i].lastack_at != -1) + continue; + + MQTT_Publish *p = &c->session->i_msgs[i].packet->publish; + arena_free(&server.mqtt_allocator, p->topic); + arena_free(&server.mqtt_allocator, p->payload); } -#if THREADSNR > 0 - pthread_mutex_unlock(&c->mutex); -#endif } -#if THREADSNR > 0 - pthread_mutex_unlock(&mutex); -#endif } /* @@ -374,298 +391,67 @@ static void inflight_msg_check(struct ev_ctx *ctx, void *data) */ /* - * All clients are pre-allocated at the start of the server, but their buffers - * (read and write) are not, they're lazily allocated with this function, meant - * to be called on the accept callback + * All clients are pre-allocated at the start of the server, but their + * buffers (read and write) are not, they're lazily allocated with this + * function, meant to be called on the accept callback */ -static void client_init(struct client *client) +static void connection_ctx_init(Connection_Context *client) { + client->allocator = + (Arena_Allocator){.alloc = free_list_alloc, + .free = free_list_free, + .context = free_list_new(1024 * 1024)}; + // client->allocator = (Pool_Allocator){ + // .alloc = arena_pool_alloc, + // .free = arena_pool_free, + // .context = memorypool_new(1024, 1024), + // }; client->online = true; client->connected = false; client->clean_session = true; - client->client_id[0] = '\0'; - client->status = WAITING_HEADER; - client->rc = 0; - client->rpos = ATOMIC_VAR_INIT(0); - client->read = ATOMIC_VAR_INIT(0); - client->toread = ATOMIC_VAR_INIT(0); - if (!client->rbuf) - client->rbuf = - try_calloc(conf->max_request_size, sizeof(unsigned char)); - client->wrote = ATOMIC_VAR_INIT(0); - client->towrite = ATOMIC_VAR_INIT(0); - if (!client->wbuf) - client->wbuf = - try_calloc(conf->max_request_size, sizeof(unsigned char)); - client->last_seen = time(NULL); - client->has_lwt = false; - client->session = NULL; - pthread_mutex_init(&client->mutex, NULL); + client->cid[0] = '\0'; + client->state = CS_OPEN; + client->read = 0; + client->written = 0; + client->write_total = 0; + client->session = NULL; } /* * As we really don't want to completely de-allocate a client in favor of - * making it reusable by another connection we simply deactivate it according - * to its state (e.g. if it's a clean_session connected client or not) and we - * allow the clients memory pool to reclaim it + * making it reusable by another connection we simply deactivate it + * according to its state (e.g. if it's a clean_session connected client or + * not) and we allow the clients memory pool to reclaim it */ -static void client_deactivate(struct client *client) +static void connection_ctx_deinit(Connection_Context *context) { -#if THREADSNR > 0 - pthread_mutex_lock(&client->mutex); -#endif - if (client->online == false) + if (context->online == false) return; - client->rpos = client->toread = client->read = 0; - client->wrote = client->towrite = 0; - close_connection(&client->conn); + context->read = 0; + context->written = context->write_total = 0; + close_connection(&context->conn); - client->online = false; + context->online = false; -#if THREADSNR > 0 - pthread_mutex_lock(&mutex); -#endif - if (client->clean_session == true) { - if (client->session) { - topic_store_remove_wildcard(server.store, client->client_id); - list_foreach(item, client->session->subscriptions) + if (context->clean_session == true) { + if (context->session) { + topic_repo_remove_wildcard(server.repo, context->cid); + list_foreach(item, context->session->subscriptions) { - topic_del_subscriber(item->data, client); + topic_del_subscriber(item->data, context); } - HASH_DEL(server.sessions, client->session); - DECREF(client->session, struct client_session); + HASH_DEL(server.sessions, context->session); } - if (client->connected == true) - HASH_DEL(server.clients_map, client); - memorypool_free(server.pool, client); - } -#if THREADSNR > 0 - pthread_mutex_unlock(&mutex); -#endif - client->connected = false; - client->client_id[0] = '\0'; -#if THREADSNR > 0 - pthread_mutex_unlock(&client->mutex); - pthread_mutex_destroy(&client->mutex); -#endif -} - -/* - * Parse packet header, it is required at least the Fixed Header of each - * packed, which is contained in the first 2 bytes in order to read packet - * type and total length that we need to recv to complete the packet. - * - * This function accept a socket fd, a buffer to read incoming streams of - * bytes and a pointer to the decoded fixed header that will be set in the - * final parsed packet. - * - * - c: A struct client pointer, contains the FD of the requesting client - * as well as his SSL context in case of TLS communication. Also it store - * the reading buffer to be used for incoming byte-streams, tracking - * read, to be read and reading position taking into account the bytes - * required to encode the packet length. - */ -static ssize_t recv_packet(struct client *c) -{ - - ssize_t nread = 0; - unsigned opcode = 0, pos = 0; - unsigned int pktlen = 0LL; - - // Base status, we have read 0 to 2 bytes - if (c->status == WAITING_HEADER) { - - /* - * Read the first two bytes, the first should contain the message type - * code - */ - nread = recv_data(&c->conn, c->rbuf + c->read, 2 - c->read); - - if (errno != EAGAIN && errno != EWOULDBLOCK && nread <= 0) - return nread == -1 ? -ERRSOCKETERR : -ERRCLIENTDC; - - c->read += nread; - - if (errno == EAGAIN && c->read < 2) - return -ERREAGAIN; - - c->status = WAITING_LENGTH; - } - - /* - * We have already read the packet HEADER, thus we know what packet we're - * dealing with, we're between bytes 2-4, as after the 1st byte, the - * remaining 3 can be all used to store the packet length, or, in case of - * ACK type packet or PINGREQ/PINGRESP and DISCONNECT, the entire packet - */ - if (c->status == WAITING_LENGTH) { - - if (c->read == 2) { - opcode = *c->rbuf >> 4; - - /* - * Check for OPCODE, if an unknown OPCODE is received return an - * error - */ - if (DISCONNECT < opcode || CONNECT > opcode) - return -ERRPACKETERR; - - /* - * We have a PINGRESP/PINGREQ or a DISCONNECT packet, we're done - * here - */ - if (opcode > UNSUBSCRIBE) { - c->rpos = 2; - c->toread = c->read; - goto exit; - } + if (context->connected == true) { + HASH_DEL(server.contexts, context); } - - /* - * Read 2 extra bytes, because the first 4 bytes could countain the - * total size in bytes of the entire packet - */ - nread = recv_data(&c->conn, c->rbuf + c->read, 4 - c->read); - - if (errno != EAGAIN && errno != EWOULDBLOCK && nread <= 0) - return nread == -1 ? -ERRSOCKETERR : -ERRCLIENTDC; - - c->read += nread; - - if (errno == EAGAIN && c->read < 4) - return -ERREAGAIN; - - /* - * Read remaining length bytes which starts at byte 2 and can be long to - * 4 bytes based on the size stored, so byte 2-5 is dedicated to the - * packet length. - */ - pktlen = mqtt_decode_length(c->rbuf + 1, &pos); - - /* - * Set return code to -ERRMAXREQSIZE in case the total packet len - * exceeds the configuration limit `max_request_size` - */ - if (pktlen > conf->max_request_size) - return -ERRMAXREQSIZE; - - /* - * Update the toread field for the client with the entire length of - * the current packet, which is comprehensive of packet length, - * bytes used to encode it and 1 byte for the header - * We've already tracked the bytes we read so far, we just need to - * read toread-read bytes. - */ - c->rpos = pos + 1; - c->toread = pktlen + pos + 1; // pos = bytes used to store length - - /* Looks like we got an ACK packet, we're done reading */ - if (pktlen <= 4) - goto exit; - - c->status = WAITING_DATA; + arena_free(&server.allocator, context); } - - /* - * Last status, we have access to the length of the packet and we know for - * sure that it's not a PINGREQ/PINGRESP/DISCONNECT packet. - */ - nread = recv_data(&c->conn, c->rbuf + c->read, c->toread - c->read); - - if (errno != EAGAIN && errno != EWOULDBLOCK && nread <= 0) - return nread == -1 ? -ERRSOCKETERR : -ERRCLIENTDC; - - c->read += nread; - if (errno == EAGAIN && c->read < c->toread) - return -ERREAGAIN; - -exit: - - return SOL_OK; -} - -/* - * Handle incoming requests, after being accepted or after a reply, under the - * hood it calls recv_packet and return an error code according to the outcome - * of the operation - */ -static inline int read_data(struct client *c) -{ - - /* - * We must read all incoming bytes till an entire packet is received. This - * is achieved by following the MQTT protocol specifications, which - * send the size of the remaining packet as the second byte. By knowing it - * we know if the packet is ready to be deserialized and used. - */ - int err = recv_packet(c); - - /* - * Looks like we got a client disconnection or If a not correct packet - * received, we must free the buffer and reset the handler to the request - * again, setting EPOLL to EPOLLIN - * - * TODO: Set a error_handler for ERRMAXREQSIZE instead of dropping client - * connection, explicitly returning an informative error code to the - * client connected. - */ - if (err < 0) - goto err; - - if (c->read < c->toread) - return -ERREAGAIN; - - info.bytes_recv += c->read; - - return SOL_OK; - - // Disconnect packet received - -err: - - return err; -} - -/* - * Write stream of bytes to a client represented by a connection object, till - * all bytes to be written is exhausted, tracked by towrite field or if an - * EAGAIN (socket descriptor must be in non-blocking mode) error is raised, - * meaning we cannot write anymore for the current cycle. - */ -static inline int write_data(struct client *c) -{ -#if THREADSNR > 0 - pthread_mutex_lock(&c->mutex); -#endif - ssize_t wrote = - send_data(&c->conn, c->wbuf + c->wrote, c->towrite - c->wrote); - if (errno != EAGAIN && errno != EWOULDBLOCK && wrote < 0) - goto clientdc; - c->wrote += wrote > 0 ? wrote : 0; - if (c->wrote < c->towrite && errno == EAGAIN) - goto eagain; - // Update information stats - info.bytes_sent += c->towrite; - // Reset client written bytes track fields - c->towrite = c->wrote = 0; -#if THREADSNR > 0 - pthread_mutex_unlock(&c->mutex); -#endif - return SOL_OK; - -clientdc: -#if THREADSNR > 0 - pthread_mutex_unlock(&c->mutex); -#endif - return -ERRSOCKETERR; - -eagain: -#if THREADSNR > 0 - pthread_mutex_unlock(&c->mutex); -#endif - return -ERREAGAIN; + context->connected = false; + context->cid[0] = '\0'; + context->state = CS_CLOSED; } /* @@ -675,23 +461,50 @@ static inline int write_data(struct client *c) */ /* - * Callback dedicated to client replies, try to send as much data as possible - * epmtying the client buffer and rearming the socket descriptor for reading - * after + * Callback dedicated to client replies, try to send as much data as + * possible emptying the client buffer and rearming the socket descriptor + * for reading after */ -static void write_callback(struct ev_ctx *ctx, void *arg) +static void send_callback(struct ev_ctx *ctx, void *arg) { - struct client *client = arg; - int err = write_data(client); + Connection_Context *client = arg; + int err = SOL_OK; + if (client->write_total == 0) + return; + /* + * Write stream of bytes to a client represented by a connection object, + * till all bytes to be written is exhausted, tracked by towrite field or if + * an EAGAIN (socket descriptor must be in non-blocking mode) error is + * raised, meaning we cannot write anymore for the current cycle. + */ + ssize_t bytes = send_data(&client->conn, client->send_buf + client->written, + client->write_total - client->written); + if (errno != EAGAIN && errno != EWOULDBLOCK && bytes < 0) { + err = -ERRSOCKETERR; + } else { + client->written += bytes > 0 ? bytes : 0; + if (client->written < client->write_total && errno == EAGAIN) + err = -ERREAGAIN; + } switch (err) { case SOL_OK: + // Update information stats + info.bytes_sent += client->write_total; + memset(client->send_buf, 0x00, BUFSIZE); + // Reset client written bytes track fields + client->write_total = client->written = 0; + /* * Rearm descriptor making it ready to receive input, * read_callback will be the callback to be used; also reset the - * read buffer status for the client. + * read buffer state for the client. */ - client->status = WAITING_HEADER; - ev_fire_event(ctx, client->conn.fd, EV_READ, read_callback, client); + if (client->data.header.bits.type == PUBLISH && + client->data.header.bits.qos == AT_MOST_ONCE) { + arena_free(&server.mqtt_allocator, client->data.publish.topic); + arena_free(&server.mqtt_allocator, client->data.publish.payload); + } + ev_oneshot(ctx, client->conn.fd, EV_READ, recv_callback, client); break; case -ERREAGAIN: /* @@ -703,10 +516,10 @@ static void write_callback(struct ev_ctx *ctx, void *arg) enqueue_event_write(client); break; default: - log_info("Closing connection with %s (%s): %s %i", client->client_id, - client->conn.ip, solerr(client->rc), err); - ev_del_fd(ctx, client->conn.fd); - client_deactivate(client); + log_info("Closing connection with %s (%s): %i", client->cid, + client->conn.ip, err); + ev_delete(ctx, client->conn.fd); + connection_ctx_deinit(client); // Update stats info.active_connections--; info.total_connections--; @@ -723,7 +536,6 @@ static void accept_callback(struct ev_ctx *ctx, void *data) { int serverfd = *((int *)data); while (1) { - /* * Accept a new incoming connection assigning ip address * and socket descriptor to the connection structure @@ -743,163 +555,193 @@ static void accept_callback(struct ev_ctx *ctx, void *data) * Create a client structure to handle his context * connection */ -#if THREADSNR > 0 - pthread_mutex_lock(&mutex); -#endif - struct client *c = memorypool_alloc(server.pool); -#if THREADSNR > 0 - pthread_mutex_unlock(&mutex); -#endif - c->conn = conn; - client_init(c); + Connection_Context *c = arena_alloc(&server.allocator, sizeof(*c)); + c->conn = conn; + connection_ctx_init(c); c->ctx = ctx; /* Add it to the epoll loop */ - ev_register_event(ctx, fd, EV_READ, read_callback, c); + ev_oneshot(ctx, fd, EV_READ, recv_callback, c); /* Record the new client connected */ info.active_connections++; info.total_connections++; log_info("[%p] Connection from %s", (void *)pthread_self(), conn.ip); + break; } } /* - * Reading packet callback, it's the main function that will be called every - * time a connected client has some data to be read, notified by the eventloop - * context. + * This function is called only if the client has sent a full stream of + * bytes consisting of a complete packet as expected by the MQTT protocol + * and by the declared length of the packet. It uses eventloop APIs to react + * accordingly to the packet type received, validating it before proceed to + * call handlers. Depending on the handler called and its outcome, it'll + * enqueue an event to write a reply or just reset the client state to allow + * reading some more packets. */ -static void read_callback(struct ev_ctx *ctx, void *data) +static int process_mqtt_message(Connection_Context *c, off_t start, + off_t offset, size_t total_len) { - struct client *c = data; - if (c->status == SENDING_DATA) - return; + u8 header = *(c->recv_buf + start); + usize total_size = total_len - (offset - start); /* - * Received a bunch of data from a client, after the creation - * of an IO event we need to read the bytes and encoding the - * content according to the protocol + * Unpack received bytes into a mqtt_packet structure and execute the + * correct handler based on the type of the operation. */ - int rc = read_data(c); + if (mqtt_read(c->recv_buf + offset, &c->data, header, total_size, + &server.mqtt_allocator) < 0) { + log_error("Unknown packet received"); + return -1; + } + int rc = handle_command(c); switch (rc) { - case SOL_OK: + case REPLY: + case MQTT_NOT_AUTHORIZED: + case MQTT_BAD_CREDENTIALS: /* - * All is ok, raise an event to the worker poll EPOLL and - * link it with the IO event containing the decode payload - * ready to be processed + * Write out to client, after a request has been processed in + * worker thread routine. Just send out all bytes stored in the + * reply buffer to the reply file descriptor. */ - /* Record last action as of now */ - c->last_seen = time(NULL); - c->status = SENDING_DATA; - process_message(ctx, c); + enqueue_event_write(c); + // Free resource, ACKs will be free'd closing the server + // if (c->data.header.bits.type != PUBLISH) + // mqtt_packet_free(&c->data); break; case -ERRCLIENTDC: - case -ERRSOCKETERR: - case -ERRPACKETERR: - case -ERRMAXREQSIZE: - // TODO move to default branch + // Client disconnected, set resources to be cleaned up + c->state = CS_CLOSING; + shutdown(c->conn.fd, SHUT_RDWR); + break; + case -ERRNOMEM: + log_error(solerr(rc)); + break; + case -20: + return -1; + default: + // if (c->data.header.bits.type != PUBLISH) + // mqtt_packet_free(&c->data); + break; + } + return 0; +} + +/* + * Reading packet callback, it's the main function that will be called every + * time a connected client has some data to be read, notified by the + * eventloop context. + */ +static void recv_callback(struct ev_ctx *ctx, void *data) +{ + Connection_Context *c = data; + if (c->state == CS_CLOSING) { + ev_delete(ctx, c->conn.fd); + connection_ctx_deinit(c); + // Update stats + info.active_connections--; + info.total_connections--; + + return; + } + + ssize_t nread = + recv_data(&c->conn, c->recv_buf + c->read, BUFSIZE - c->read); + + if (nread <= 0) { + // No data available right now + if (nread == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + /* + * We have an EAGAIN error, which is really just signaling that + * for some reasons the kernel is not ready to read more bytes at + * the moment and it would block, so we just want to re-try some + * time later, re-enqueuing a new read event + */ + // ev_oneshot(ctx, c->conn.fd, EV_READ, recv_callback, c); + return; + } + /* * We got an unexpected error or a disconnection from the * client side, remove client from the global map and * free resources allocated such as io_event structure and * paired payload */ - log_error("Closing connection with %s (%s): %s", c->client_id, - c->conn.ip, solerr(rc)); -#if THREADSNR > 0 - pthread_mutex_lock(&mutex); -#endif + log_error("Closing connection with %s (%s): %s", c->cid, c->conn.ip, + solerr(nread)); // Publish, if present, LWT message - if (c->has_lwt == true) { - char *tname = (char *)c->session->lwt_msg.publish.topic; - struct topic *t = topic_store_get(server.store, tname); + if (c->session->lwt) { + char *tname = (char *)c->session->lwt->publish.topic; + Topic *t = topic_repo_fetch(server.repo, tname); if (t) - publish_message(&c->session->lwt_msg, t); + publish_message(c->session->lwt, t, &c->allocator); } // Clean resources - ev_del_fd(ctx, c->conn.fd); + ev_delete(ctx, c->conn.fd); // Remove from subscriptions for now if (c->session && list_size(c->session->subscriptions) > 0) { list_foreach(item, c->session->subscriptions) { - log_debug("Deleting %s from topic %s", c->client_id, - ((struct topic *)item->data)->name); + log_debug("Deleting %s from topic %s", c->cid, + ((Topic *)item->data)->name); topic_del_subscriber(item->data, c); } } -#if THREADSNR > 0 - pthread_mutex_unlock(&mutex); -#endif - client_deactivate(c); + connection_ctx_deinit(c); info.active_connections--; info.total_connections--; - break; - case -ERREAGAIN: - /* - * We have an EAGAIN error, which is really just signaling that - * for some reasons the kernel is not ready to read more bytes at - * the moment and it would block, so we just want to re-try some - * time later, re-enqueuing a new read event - */ - ev_fire_event(ctx, c->conn.fd, EV_READ, read_callback, c); - break; + + return; } -} -/* - * This function is called only if the client has sent a full stream of bytes - * consisting of a complete packet as expected by the MQTT protocol and by the - * declared length of the packet. - * It uses eventloop APIs to react accordingly to the packet type received, - * validating it before proceed to call handlers. Depending on the handler - * called and its outcome, it'll enqueue an event to write a reply or just - * reset the client state to allow reading some more packets. - */ -static void process_message(struct ev_ctx *ctx, struct client *c) -{ - struct io_event io = {.client = c}; - /* - * Unpack received bytes into a mqtt_packet structure and execute the - * correct handler based on the type of the operation. - */ - mqtt_unpack(c->rbuf + c->rpos, &io.data, *c->rbuf, c->read - c->rpos); - c->toread = c->read = c->rpos = 0; - c->rc = handle_command(io.data.header.bits.type, &io); - switch (c->rc) { - case REPLY: - case MQTT_NOT_AUTHORIZED: - case MQTT_BAD_USERNAME_OR_PASSWORD: + c->read += nread; // Update the amount of data read + info.bytes_recv += nread; + size_t processed = 0; + + // Process all complete packets in the buffer + while (processed < c->read) { + if (c->read - processed < 1) // Not enough for a header + break; + + size_t offset = processed + sizeof(u8); + unsigned pos = 0; /* - * Write out to client, after a request has been processed in - * worker thread routine. Just send out all bytes stored in the - * reply buffer to the reply file descriptor. + * Read remaining length bytes which starts at byte 2 and can be + * long to 4 bytes based on the size stored, so byte 2-5 is + * dedicated to the packet length. */ - enqueue_event_write(c); - /* Free resource, ACKs will be free'd closing the server */ - if (io.data.header.bits.type != PUBLISH) - mqtt_packet_destroy(&io.data); - break; - case -ERRCLIENTDC: - ev_del_fd(ctx, c->conn.fd); - client_deactivate(io.client); - // Update stats - info.active_connections--; - info.total_connections--; - break; - case -ERRNOMEM: - log_error(solerr(c->rc)); - break; - default: - c->status = WAITING_HEADER; - if (io.data.header.bits.type != PUBLISH) - mqtt_packet_destroy(&io.data); - break; + size_t variable_len = mqtt_read_length(c->recv_buf + offset, &pos); + // Total packet length + size_t total_len = variable_len + pos + sizeof(u8); + + if (c->read - processed < total_len) // Incomplete packet + break; + + offset += pos; + + // Process the complete packet + if (process_mqtt_message(c, processed, offset, total_len) < 0) + break; + processed += total_len; // Advance to the next packet + } + + // Handle leftover data + size_t leftover = c->read - processed; + if (leftover > 0) { + // Move leftover data to the start + memmove(c->recv_buf, c->recv_buf + processed, leftover); } + c->read = leftover > 0 ? leftover : 0; + + memset(c->recv_buf + c->read, 0x00, BUFSIZE - c->read); + + ev_oneshot(ctx, c->conn.fd, EV_READ, recv_callback, c); } /* - * Eventloop stop callback, will be triggered by an EV_CLOSEFD event and stop - * the running loop, unblocking the call. + * Eventloop stop callback, will be triggered by an EV_CLOSEFD event and + * stop the running loop, unblocking the call. */ static void stop_handler(struct ev_ctx *ctx, void *arg) { @@ -925,20 +767,20 @@ static void eventloop_start(void *args) ev_register_event(&ctx, conf->run, EV_CLOSEFD | EV_READ, stop_handler, NULL); #else - ev_register_event(&ctx, conf->run[1], EV_CLOSEFD | EV_READ, stop_handler, - NULL); + ev_add_event(&ctx, conf->run[1], EV_CLOSEFD | EV_READ, stop_handler, NULL); #endif // Register listening FD with accept callback - ev_register_event(&ctx, sfd, EV_READ, accept_callback, &sfd); + ev_add_event(&ctx, sfd, EV_READ, accept_callback, &sfd); // Register periodic tasks if (loop_data->cronjobs == true) { - ev_register_cron(&ctx, publish_stats, NULL, conf->stats_pub_interval, - 0); - ev_register_cron(&ctx, inflight_msg_check, NULL, 1, 0); + ev_add_cron(&ctx, publish_stats, NULL, conf->stats_pub_interval, 0); + ev_add_cron(&ctx, inflight_message_check, NULL, 1, 0); } + ev_add_cron(&ctx, sweep_completed_inflights, NULL, 0, 1000); // Start the loop, blocking call - ev_run(&ctx); - ev_destroy(&ctx); + if (ev_run(&ctx) < 0) + log_error("Event loop: %s", strerror(errno)); + ev_free(&ctx); } /* @@ -952,32 +794,60 @@ static void eventloop_start(void *args) * schedules an EV_WRITE event with a client pointer set to write carried * contents out on the socket descriptor. */ -void enqueue_event_write(const struct client *c) +void enqueue_event_write(const Connection_Context *c) { - ev_fire_event(c->ctx, c->conn.fd, EV_WRITE, write_callback, (void *)c); + if (c->state == CS_CLOSING) { + ev_delete(c->ctx, c->conn.fd); + connection_ctx_deinit((Connection_Context *)c); + // Update stats + info.active_connections--; + info.total_connections--; + return; + } + if (ev_oneshot(c->ctx, c->conn.fd, EV_WRITE, send_callback, (void *)c) < 0) + fprintf(stderr, "error setting EV_WRITE\n"); } /* * Main entry point for the server, to be called with an address and a port - * to start listening. The function may fail only in the case of Out of memory - * error occurs or listen call fails, on the other cases it should just log - * unexpected errors. + * to start listening. The function may fail only in the case of Out of + * memory error occurs or listen call fails, on the other cases it should + * just log unexpected errors. */ int start_server(const char *addr, const char *port) { + general_allocator = + (Arena_Allocator){.alloc = free_list_alloc, + .free = free_list_free, + .context = free_list_new(1024 * 1024)}; INIT_INFO; /* Initialize global Sol instance */ - server.store = topic_store_new(); + server.repo = topic_repo_new(); server.auths = NULL; - server.pool = memorypool_new(BASE_CLIENTS_NUM, sizeof(struct client)); - if (!server.pool) - log_fatal("Failed to allocate %d sized memory pool for clients", - BASE_CLIENTS_NUM); - server.clients_map = NULL; - server.sessions = NULL; - pthread_mutex_init(&mutex, NULL); + server.allocator = + (Arena_Allocator){.alloc = free_list_alloc, + .free = free_list_free, + .context = free_list_new(BASE_CLIENTS_NUM * + sizeof(Connection_Context))}; + server.session_allocator = (Arena_Allocator){ + .alloc = free_list_alloc, + .free = free_list_free, + .context = free_list_new(BASE_CLIENTS_NUM * sizeof(Session))}; + + server.mqtt_allocator = + (Arena_Allocator){.alloc = free_list_alloc, + .free = free_list_free, + .context = free_list_new(1024 * 1024 * 256)}; + + server.packet_allocator = (Pool_Allocator){ + .alloc = arena_pool_alloc, + .free = arena_pool_free, + .context = memorypool_new(BASE_CLIENTS_NUM, sizeof(MQTT_Packet)), + }; + server.contexts = NULL; + server.sessions = NULL; if (conf->allow_anonymous == false) if (!config_read_passwd_file(conf->password_file, &server.auths)) @@ -985,10 +855,10 @@ int start_server(const char *addr, const char *port) /* Generate stats topics */ for (int i = 0; i < SYS_TOPICS; i++) { - struct topic *t = topic_new(try_strdup(sys_topics[i].name)); + Topic *t = topic_new(try_strdup(sys_topics[i].name)); if (!t) log_fatal("start_server failed: Out of memory"); - topic_store_put(server.store, t); + topic_repo_put(server.repo, t); } /* Start listening for new connections */ @@ -1005,35 +875,21 @@ int start_server(const char *addr, const char *port) log_info("Server start"); info.start_time = time(NULL); - struct listen_payload loop_start = {sfd, ATOMIC_VAR_INIT(false)}; + struct listen_payload loop_start = {sfd, false}; -#if THREADSNR > 0 - pthread_t thrs[THREADSNR]; - for (int i = 0; i < THREADSNR; ++i) { - pthread_create(&thrs[i], NULL, (void *(*)(void *)) & eventloop_start, - &loop_start); - } -#endif - loop_start.cronjobs = true; + loop_start.cronjobs = true; // start eventloop, could be spread on multiple threads eventloop_start(&loop_start); -#if THREADSNR > 0 - for (int i = 0; i < THREADSNR; ++i) - pthread_join(thrs[i], NULL); -#endif - close(sfd); AUTH_DESTROY(server.auths); - topic_store_destroy(server.store); + topic_repo_free(server.repo); /* Destroy SSL context, if any present */ if (conf->tls == true) { SSL_CTX_free(server.ssl_ctx); openssl_cleanup(); } - pthread_mutex_destroy(&mutex); - log_info("Sol v%s exiting", VERSION); return SOL_OK; diff --git a/src/sol.c b/src/sol.c index 314fdf9..1c40e4a 100644 --- a/src/sol.c +++ b/src/sol.c @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -32,21 +32,18 @@ #include "config.h" #include "logging.h" #include "server.h" -#include "util.h" #include // Stops epoll_wait loops by sending an event static void sigint_handler(int signum) { (void)signum; - for (int i = 0; i < THREADSNR + 1; ++i) { #ifdef __linux__ - eventfd_write(conf->run, 1); + eventfd_write(conf->run, 1); #else - (void)write(conf->run[0], &(unsigned long){1}, sizeof(unsigned long)); + (void)write(conf->run[0], &(unsigned long){1}, sizeof(unsigned long)); #endif - usleep(1500); - } + usleep(1500); } static const char *flag_description[] = { diff --git a/src/subscriber.c b/src/subscriber.c index 967c93c..24e8ce1 100644 --- a/src/subscriber.c +++ b/src/subscriber.c @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -28,21 +28,17 @@ #include "memory.h" #include "sol_internal.h" -static void subscriber_destroy(const struct ref *); - /* * Allocate memory on the heap to create and return a pointer to a struct * subscriber, assigining the passed in QoS, session pointer, and * instantiating a reference counter to 0. * It may fail as it needs to allocate some bytes on the heap. */ -struct subscriber *subscriber_new(struct client_session *s, unsigned char qos) +Subscriber *subscriber_new(Session *s, unsigned char qos) { - struct subscriber *sub = try_alloc(sizeof(*sub)); - sub->session = s; - sub->granted_qos = qos; - sub->refcount = (struct ref){.count = 0, .free = subscriber_destroy}; - memcpy(sub->id, s->session_id, MQTT_CLIENT_ID_LEN); + Subscriber *sub = try_alloc(sizeof(*sub)); + sub->session = s; + sub->granted_qos = qos; return sub; } @@ -53,13 +49,11 @@ struct subscriber *subscriber_new(struct client_session *s, unsigned char qos) * allocated pointer is returned. * It may fail as it needs to allocate some bytes on the heap. */ -struct subscriber *subscriber_clone(const struct subscriber *s) +Subscriber *subscriber_clone(const Subscriber *s) { - struct subscriber *sub = try_alloc(sizeof(*sub)); - sub->session = s->session; - sub->granted_qos = s->granted_qos; - sub->refcount = (struct ref){.count = 0, .free = subscriber_destroy}; - memcpy(sub->id, s->id, MQTT_CLIENT_ID_LEN); + Subscriber *sub = try_alloc(sizeof(*sub)); + sub->session = s->session; + sub->granted_qos = s->granted_qos; return sub; } @@ -67,19 +61,9 @@ struct subscriber *subscriber_clone(const struct subscriber *s) * Checks if a client is subscribed to a topic by trying to fetch the * client_session by its ID on the subscribers inner hashmap of the topic. */ -bool is_subscribed(const struct topic *t, const struct client_session *s) +bool is_subscribed(const Topic *t, const Session *s) { - struct subscriber *dummy = NULL; - HASH_FIND_STR(t->subscribers, s->session_id, dummy); + Subscriber *dummy = NULL; + HASH_FIND_STR(t->subscribers, s->cid, dummy); return dummy != NULL; } - -/* - * Auxiliary function, defines the destructor behavior for subscriber, just - * decreasing the reference counter till 0, then free the memory. - */ -static void subscriber_destroy(const struct ref *r) -{ - struct subscriber *sub = container_of(r, struct subscriber, refcount); - free_memory(sub); -} diff --git a/src/topic.c b/src/topic.c index 397b81c..c9ba1a0 100644 --- a/src/topic.c +++ b/src/topic.c @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,12 +29,12 @@ #include "sol_internal.h" /* - * Initialize a struct topic pointer by setting its name, subscribers and + * Initialize a Topic pointer by setting its name, subscribers and * retained_msg are set to NULL. * The function expects a non-null pointer and can't fail, if a null topic * is passed, the function return prematurely. */ -void topic_init(struct topic *t, const char *name) +void topic_init(Topic *t, const char *name) { if (!t) return; @@ -48,9 +48,9 @@ void topic_init(struct topic *t, const char *name) * to it. The function can fail as a memory allocation is requested, if it * fails the program execution graceful crash. */ -struct topic *topic_new(const char *name) +Topic *topic_new(const char *name) { - struct topic *t = try_alloc(sizeof(*t)); + Topic *t = try_alloc(sizeof(*t)); topic_init(t, name); return t; } @@ -58,7 +58,7 @@ struct topic *topic_new(const char *name) /* * Deallocate the topic name, retained_msg and all its subscribers */ -void topic_destroy(struct topic *t) +void topic_free(Topic *t) { if (!t) return; @@ -68,7 +68,7 @@ void topic_destroy(struct topic *t) free_memory(t); return; } - struct subscriber *sub, *dummy; + Subscriber *sub, *dummy; HASH_ITER(hh, t->subscribers, sub, dummy) { if (!sub) @@ -85,14 +85,12 @@ void topic_destroy(struct topic *t) * The function can fail as a memory allocation is requested, if it fails the * program execution graceful crash. */ -struct subscriber *topic_add_subscriber(struct topic *t, - struct client_session *s, - unsigned char qos) +Subscriber *topic_add_subscriber(Topic *t, Session *s, unsigned char qos) { - struct subscriber *sub = subscriber_new(s, qos), *tmp; - HASH_FIND_STR(t->subscribers, sub->id, tmp); + Subscriber *sub = subscriber_new(s, qos), *tmp; + HASH_FIND_STR(t->subscribers, sub->session->cid, tmp); if (!tmp) - HASH_ADD_STR(t->subscribers, id, sub); + HASH_ADD_STR(t->subscribers, session->cid, sub); return sub; } @@ -101,15 +99,14 @@ struct subscriber *topic_add_subscriber(struct topic *t, * the client_id belonging to the client pointer passed in. * The subscriber deletion is really a reference count subtraction, DECREF * macro takes care of the counter, if it reaches 0 it de-allocates the memory - * reserved to the struct subscriber. + * reserved to the Subscriber. * The function can't fail. */ -void topic_del_subscriber(struct topic *t, struct client *c) +void topic_del_subscriber(Topic *t, Connection_Context *c) { - struct subscriber *sub = NULL; - HASH_FIND_STR(t->subscribers, c->client_id, sub); + Subscriber *sub = NULL; + HASH_FIND_STR(t->subscribers, c->cid, sub); if (sub) { HASH_DEL(t->subscribers, sub); - DECREF(sub, struct subscriber); } } diff --git a/src/topic_store.c b/src/topic_repo.c similarity index 70% rename from src/topic_store.c rename to src/topic_repo.c index ee4c5df..be90431 100644 --- a/src/topic_store.c +++ b/src/topic_repo.c @@ -1,7 +1,7 @@ /* * BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -30,6 +30,7 @@ #include "memory.h" #include "sol_internal.h" #include "trie.h" +#include "util.h" static int wildcard_destructor(struct list_node *); @@ -43,11 +44,11 @@ static int subscription_cmp(const void *, const void *); * wildcard topics. * The function may gracefully crash as the memory allocation may fail. */ -struct topic_store *topic_store_new(void) +Topic_Repo *topic_repo_new(void) { - struct topic_store *store = try_alloc(sizeof(*store)); - store->topics = trie_new(topic_destructor); - store->wildcards = list_new(wildcard_destructor); + Topic_Repo *store = try_alloc(sizeof(*store)); + store->topics = trie_new(topic_destructor); + store->wildcards = list_new(wildcard_destructor); return store; } @@ -55,17 +56,17 @@ struct topic_store *topic_store_new(void) * Deallocate heap memory for the list and every wildcard item stored into, * also the store is deallocated */ -void topic_store_destroy(struct topic_store *store) +void topic_repo_free(Topic_Repo *store) { - list_destroy(store->wildcards, 1); - trie_destroy(store->topics); + list_free(store->wildcards, 1); + trie_free(store->topics); free_memory(store); } /* * Insert a topic into the store or update it if already present */ -void topic_store_put(struct topic_store *store, struct topic *t) +void topic_repo_put(Topic_Repo *store, Topic *t) { trie_insert(store->topics, t->name, t); } @@ -73,7 +74,7 @@ void topic_store_put(struct topic_store *store, struct topic *t) /* * Remove a topic into the store */ -void topic_store_del(struct topic_store *store, const char *name) +void topic_repo_delete(Topic_Repo *store, const char *name) { trie_delete(store->topics, name); } @@ -81,9 +82,9 @@ void topic_store_del(struct topic_store *store, const char *name) /* * Check if the store contains a topic by name key */ -bool topic_store_contains(const struct topic_store *store, const char *name) +bool topic_repo_contains(const Topic_Repo *store, const char *name) { - struct topic *t = topic_store_get(store, name); + Topic *t = topic_repo_fetch(store, name); return t != NULL; } @@ -91,9 +92,9 @@ bool topic_store_contains(const struct topic_store *store, const char *name) * Return a topic associated to a topic name from the store, returns NULL if no * topic is found. */ -struct topic *topic_store_get(const struct topic_store *store, const char *name) +Topic *topic_repo_fetch(const Topic_Repo *store, const char *name) { - struct topic *ret_topic; + Topic *ret_topic; trie_find(store->topics, name, (void *)&ret_topic); return ret_topic; } @@ -105,14 +106,13 @@ struct topic *topic_store_get(const struct topic_store *store, const char *name) * The function may fail as in case of no topic found it tries to allocate * space on the heap for the new inserted topic. */ -struct topic *topic_store_get_or_put(struct topic_store *store, - const char *name) +Topic *topic_repo_fetch_default(Topic_Repo *store, const char *name) { - struct topic *t = topic_store_get(store, name); + Topic *t = topic_repo_fetch(store, name); if (t != NULL) return t; t = topic_new(try_strdup(name)); - topic_store_put(store, t); + topic_repo_put(store, t); return t; } @@ -120,7 +120,7 @@ struct topic *topic_store_get_or_put(struct topic_store *store, * Add a wildcard topic to the topic_store struct, does not check if it already * exists */ -void topic_store_add_wildcard(struct topic_store *store, struct subscription *s) +void topic_repo_add_wildcard(Topic_Repo *store, Subscription *s) { store->wildcards = list_push(store->wildcards, s); } @@ -128,7 +128,7 @@ void topic_store_add_wildcard(struct topic_store *store, struct subscription *s) /* * Remove a wildcard by id key from the topic_store struct */ -void topic_store_remove_wildcard(struct topic_store *store, char *id) +void topic_repo_remove_wildcard(Topic_Repo *store, char *id) { list_remove(store->wildcards, id, subscription_cmp); } @@ -137,8 +137,8 @@ void topic_store_remove_wildcard(struct topic_store *store, char *id) * Run a function to each node of the topic_store trie holding the topic * entries */ -void topic_store_map(struct topic_store *store, const char *prefix, - void (*fn)(struct trie_node *, void *), void *arg) +void topic_repo_map(Topic_Repo *store, const char *prefix, + void (*fn)(struct trie_node *, void *), void *arg) { trie_prefix_map(store->topics->root, prefix, fn, arg); } @@ -146,21 +146,20 @@ void topic_store_map(struct topic_store *store, const char *prefix, /* * Check if the wildcards list of the topic_store is empty */ -bool topic_store_wildcards_empty(const struct topic_store *store) +bool topic_repo_wildcards_empty(const Topic_Repo *store) { return list_size(store->wildcards) == 0; } /* * Auxiliary function, destructor to be passed in to init a list structure, - * this one is used to correctly destroy struct subscription items + * this one is used to correctly destroy Subscription items */ static int wildcard_destructor(struct list_node *node) { if (!node) return -SOL_ERR; - struct subscription *s = node->data; - DECREF(s->subscriber, struct subscriber); + Subscription *s = node->data; free_memory((char *)s->topic); free_memory(s); free_memory(node); @@ -175,8 +174,8 @@ static bool topic_destructor(struct trie_node *node, bool flag) { if (!node || !node->data) return false; - struct topic *t = node->data; - topic_destroy(t); + Topic *t = node->data; + topic_free(t); return true; } @@ -186,7 +185,7 @@ static bool topic_destructor(struct trie_node *node, bool flag) */ static int subscription_cmp(const void *ptr_s1, const void *ptr_s2) { - struct subscription *s1 = ((struct list_node *)ptr_s1)->data; - const char *id = ptr_s2; - return STREQ(s1->subscriber->id, id, MQTT_CLIENT_ID_LEN); + Subscription *s1 = ((struct list_node *)ptr_s1)->data; + const char *id = ptr_s2; + return STREQ(s1->subscriber->session->cid, id, MQTT_CLIENT_ID_LEN); } diff --git a/src/trie.c b/src/trie.c index 5c64497..bf69dc0 100644 --- a/src/trie.c +++ b/src/trie.c @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -27,12 +27,11 @@ #include "trie.h" #include "memory.h" -#include "util.h" #include #include // Private functions declaration -static void children_destroy(struct bst_node *, size_t *, trie_destructor *); +static void children_free(struct bst_node *, size_t *, trie_destructor *); static int trie_node_count(const struct trie_node *); static void trie_node_prefix_find(const struct trie_node *, char *, int, List *); @@ -253,7 +252,7 @@ void trie_prefix_delete(Trie *trie, const char *prefix) return; } - children_destroy(cursor->children, &trie->size, trie->destructor); + children_free(cursor->children, &trie->size, trie->destructor); cursor->children = NULL; trie_delete(trie, prefix); @@ -350,22 +349,22 @@ List *trie_prefix_find(const Trie *trie, const char *prefix) return keys; } -static void children_destroy(struct bst_node *node, size_t *len, - trie_destructor *destructor) +static void children_free(struct bst_node *node, size_t *len, + trie_destructor *destructor) { if (!node) return; - trie_node_destroy(node->data, len, destructor); + trie_node_free(node->data, len, destructor); if (node->left) - children_destroy(node->left, len, destructor); + children_free(node->left, len, destructor); if (node->right) - children_destroy(node->right, len, destructor); + children_free(node->right, len, destructor); free_memory(node); } /* Release memory of a node while updating size of the trie */ -void trie_node_destroy(struct trie_node *node, size_t *size, - trie_destructor *destructor) +void trie_node_free(struct trie_node *node, size_t *size, + trie_destructor *destructor) { // Base case @@ -373,7 +372,7 @@ void trie_node_destroy(struct trie_node *node, size_t *size, return; // Recursive call to all children of the node - children_destroy(node->children, size, destructor); + children_free(node->children, size, destructor); node->children = NULL; if (destructor) { @@ -389,11 +388,11 @@ void trie_node_destroy(struct trie_node *node, size_t *size, free_memory(node); } -void trie_destroy(Trie *trie) +void trie_free(Trie *trie) { if (!trie) return; - trie_node_destroy(trie->root, &(trie->size), trie->destructor); + trie_node_free(trie->root, &(trie->size), trie->destructor); free_memory(trie); } diff --git a/src/util.c b/src/util.c index 6e1ef94..2acfcd4 100644 --- a/src/util.c +++ b/src/util.c @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan All rights reserved. + * Copyright (c) 2025, Andrea Giacomo Baldan All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,7 +33,6 @@ // #include #include #include -#include #include #include #include @@ -88,8 +87,8 @@ char *update_integer_string(char *str, int num) * Check for realloc if the new value is "larger" then * previous */ - char tmp[number_len(n) + 1]; // max size in bytes - sprintf(tmp, "%d", n); // XXX Unsafe + char tmp[number_len(n) + 1]; // max size in bytes + snprintf(tmp, number_len(n), "%d", n); // XXX Unsafe size_t len = strlen(tmp); str = try_realloc(str, len + 1); memcpy(str, tmp, len + 1); diff --git a/tests/sol_test.c b/tests/sol_test.c index 613bf64..954edb4 100644 --- a/tests/sol_test.c +++ b/tests/sol_test.c @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan + * Copyright (c) 2025, Andrea Giacomo Baldan * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/tests/structures_test.c b/tests/structures_test.c index 24412c1..f101a30 100644 --- a/tests/structures_test.c +++ b/tests/structures_test.c @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan + * Copyright (c) 2025, Andrea Giacomo Baldan * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -27,10 +27,10 @@ */ #include "structures_test.h" -#include "../src/iterator.h" -#include "../src/list.h" -#include "../src/memory.h" -#include "../src/trie.h" +#include "../include/iterator.h" +#include "../include/list.h" +#include "../include/memory.h" +#include "../include/trie.h" #include "unit.h" #include #include @@ -42,7 +42,7 @@ static char *test_list_new(void) { List *l = list_new(NULL); ASSERT("list::list_new...FAIL", l != NULL); - list_destroy(l, 0); + list_free(l, 0); printf("list::list_new...OK\n"); return 0; } @@ -54,7 +54,7 @@ static char *test_list_destroy(void) { List *l = list_new(NULL); ASSERT("list::list_destroy...FAIL", l != NULL); - list_destroy(l, 0); + list_free(l, 0); printf("list::list_destroy...OK\n"); return 0; } @@ -68,7 +68,7 @@ static char *test_list_push(void) char *x = "abc"; list_push(l, x); ASSERT("list::list_push...FAIL", l->len == 1); - list_destroy(l, 0); + list_free(l, 0); printf("list::list_push...OK\n"); return 0; } @@ -82,7 +82,7 @@ static char *test_list_push_back(void) char *x = "abc"; list_push_back(l, x); ASSERT("list::list_push_back...FAIL", l->len == 1); - list_destroy(l, 0); + list_free(l, 0); printf("list::list_push_back...OK\n"); return 0; } @@ -107,7 +107,7 @@ static char *test_list_remove_node(void) struct list_node *node = list_remove_node(l, x, compare_str); ASSERT("list::list_remove_node...FAIL", strcmp(node->data, x) == 0); free_memory(node); - list_destroy(l, 0); + list_free(l, 0); printf("list::list_remove_node...OK\n"); return 0; } @@ -123,8 +123,8 @@ static char *test_list_iterator(void) struct iterator *it = iter_new(l, list_iter_next); ASSERT("list::list_iterator::list_iter_next...FAIL", strcmp(it->ptr, x) == 0); - list_destroy(l, 0); - iter_destroy(it); + list_free(l, 0); + iter_free(it); printf("list::list_iterator...OK\n"); return 0; } @@ -136,7 +136,7 @@ static char *test_trie_new(void) { struct Trie *trie = trie_new(NULL); ASSERT("trie::trie_new...FAIL", trie != NULL); - trie_destroy(trie); + trie_free(trie); printf("trie::trie_new...OK\n"); return 0; } @@ -149,7 +149,7 @@ static char *test_trie_create_node(void) struct trie_node *node = trie_create_node('a'); size_t size = 0; ASSERT("trie::trie_create_node...FAIL", node != NULL); - trie_node_destroy(node, &size, NULL); + trie_node_free(node, &size, NULL); printf("trie::trie_create_node...OK\n"); return 0; } @@ -166,7 +166,7 @@ static char *test_trie_insert(void) void *payload = NULL; bool found = trie_find(root, key, &payload); ASSERT("trie::trie_insert...FAIL", (found == true && payload != NULL)); - trie_destroy(root); + trie_free(root); printf("trie::trie_insert...OK\n"); return 0; } @@ -183,7 +183,7 @@ static char *test_trie_find(void) void *payload = NULL; bool found = trie_find(root, key, &payload); ASSERT("trie::trie_find...FAIL", (found == true && payload != NULL)); - trie_destroy(root); + trie_free(root); printf("trie::trie_find...OK\n"); return 0; } @@ -213,7 +213,7 @@ static char *test_trie_delete(void) ASSERT("trie::trie_delete...FAIL", (found == false || payload == NULL)); found = trie_find(root, key3, &payload); ASSERT("trie::trie_delete...FAIL", (found == false || payload == NULL)); - trie_destroy(root); + trie_free(root); printf("trie::trie_delete...OK\n"); return 0; } @@ -250,7 +250,7 @@ static char *test_trie_prefix_delete(void) found = trie_find(root, key4, &payload); ASSERT("trie::trie_prefix_delete...FAIL", (found == true || payload != NULL)); - trie_destroy(root); + trie_free(root); printf("trie::trie_prefix_delete...OK\n"); return 0; } @@ -277,7 +277,7 @@ static char *test_trie_prefix_count(void) ASSERT("trie::trie_prefix_count...FAIL", count == 4); count = trie_prefix_count(root, "helloworld!"); ASSERT("trie::trie_prefix_count...FAIL", count == 0); - trie_destroy(root); + trie_free(root); printf("trie::trie_prefix_count...OK\n"); return 0; } diff --git a/tests/unit.h b/tests/unit.h index 49c25eb..598a0ee 100644 --- a/tests/unit.h +++ b/tests/unit.h @@ -1,6 +1,6 @@ /* BSD 2-Clause License * - * Copyright (c) 2023, Andrea Giacomo Baldan + * Copyright (c) 2025, Andrea Giacomo Baldan * All rights reserved. * * Redistribution and use in source and binary forms, with or without