diff options
Diffstat (limited to 'include/hybbx')
56 files changed, 4420 insertions, 0 deletions
diff --git a/include/hybbx/ardop.h b/include/hybbx/ardop.h new file mode 100644 index 0000000..b0869b0 --- /dev/null +++ b/include/hybbx/ardop.h @@ -0,0 +1,50 @@ +#ifndef HYBBX_ARDOP_H +#define HYBBX_ARDOP_H + +/** + * ARDOP link adapter — HyBBX Host-Client subset (external ARDOP modem). + * + * HyBBX is never a sound-modem service. Operator runs ARDOPC or ardopcf as a + * separate process; this plugin speaks the TNC Host Interface over TCP. + * For CRDOP, use the `crdop` plugin and external CRDOPC instead. + */ + +#include "hybbx/types.h" +#include "hybbx/crdop.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Default ARDOPC TCP control port (data = port + 1). */ +#define HYBBX_ARDOP_DEFAULT_PORT 8515u + +/** Max payload per ARDOP host data frame (spec timing guidance). */ +#define HYBBX_ARDOP_DATA_MAX 2000u + +typedef struct hybbx_ardop_config { + char *ardop_host; + unsigned ardop_port; + char *mycall; + char *arq_bandwidth; + char *peer_call; + int listen; + char *circuit_host; + unsigned circuit_port; + char *link_id; + char *link_password; + char *link_role; + double frequency_mhz; + /** Experimental CRDOP preview: cb | amateur (see docs/CRDOP.md). */ + hybbx_crdop_radio_profile_t radio_profile; +} hybbx_ardop_config_t; + +hybbx_result_t hybbx_ardop_config_parse(const char *config, + hybbx_ardop_config_t *out); +void hybbx_ardop_config_free(hybbx_ardop_config_t *config); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_ARDOP_H */ diff --git a/include/hybbx/auth.h b/include/hybbx/auth.h new file mode 100644 index 0000000..10c27ce --- /dev/null +++ b/include/hybbx/auth.h @@ -0,0 +1,117 @@ +#ifndef HYBBX_AUTH_H +#define HYBBX_AUTH_H + +#include "hybbx/password.h" + +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define HYBBX_AUTH_GUEST_PREFIX_MAX 32 + +/** Default guest name prefix: Guest1, Guest2, … */ +#define HYBBX_AUTH_DEFAULT_GUEST_PREFIX "Guest" + +/** Highest guest number issued (Guest1 … Guest25); max simultaneous guests. */ +#define HYBBX_GUEST_NUMBER_MAX 25u + +/** + * Synthetic user IDs for ephemeral guest sessions (not stored in user files). + * Slot @p n is 1 … @ref HYBBX_GUEST_NUMBER_MAX. + */ +#define HYBBX_GUEST_USER_ID(n) (0xffffffffffffff00ULL + (uint64_t)(n)) + +#define HYBBX_USERNAME_MIN_LEN 4u +#define HYBBX_USERNAME_MAX_LEN 12u +#define HYBBX_USERNAME_MAX_DIGITS 4u + +/** Default Sysop account (auto-created when no Sysop exists in the user database). */ +#define HYBBX_DEFAULT_SYSOP_USERNAME "Sysop" +#define HYBBX_SYSOP_INIT_PASSWORD_MIN 10u +#define HYBBX_SYSOP_INIT_PASSWORD_MAX 14u + +/** Plain-text password length policy for new passwords (/changeme, future set-password). */ +#define HYBBX_PASSWORD_MIN_LEN 8u +#define HYBBX_PASSWORD_MAX_LEN 24u + +/** + * User privilege levels (highest to lowest). + * Sysop: exactly one account on the system. + */ +typedef enum hybbx_user_level { + HYBBX_LEVEL_SYSOP = 1, + HYBBX_LEVEL_ADMIN = 2, + HYBBX_LEVEL_MOD = 3, + HYBBX_LEVEL_USER = 4, + HYBBX_LEVEL_GUEST = 5 +} hybbx_user_level_t; + +typedef struct hybbx_auth_config { + int auto_login; + char guest_prefix[HYBBX_AUTH_GUEST_PREFIX_MAX]; +} hybbx_auth_config_t; + +void hybbx_auth_config_defaults(hybbx_auth_config_t *auth); + +const char *hybbx_user_level_name(hybbx_user_level_t level); +hybbx_user_level_t hybbx_user_level_parse(const char *name); +int hybbx_user_level_is_guest(hybbx_user_level_t level); + +/** Non-zero when @p level is Sysop or Admin. */ +int hybbx_user_level_is_sysop_or_admin(hybbx_user_level_t level); + +/** Non-zero when @p level is the protected Sysop account tier. */ +int hybbx_user_level_is_sysop(hybbx_user_level_t level); + +/** Non-zero when @p password meets the plain-text password policy (8–24 characters). */ +int hybbx_password_plain_valid(const char *password); + +/** + * Return non-zero when @p username is valid for registration: + * 4–12 characters, letters (a–z), digits (max 4), and at most one `_` or one `-` + * (not both); case-insensitive. + */ +int hybbx_username_valid(const char *username, const char *guest_prefix); + +/** Fold @p username to lowercase in place (for case-insensitive lookup). */ +void hybbx_username_normalize(char *username); + +/** Presentation form for output (e.g. Sysop instead of stored sysop). */ +const char *hybbx_username_display(const char *username, + hybbx_user_level_t level); + +struct hybbx_user_record; + +/** User-facing display name (nickname when set, else username). */ +const char *hybbx_user_display_name(const struct hybbx_user_record *user); + +/** + * Derive a default nickname from a legacy stored username when none is set. + */ +void hybbx_nickname_infer(const char *stored_username, + char *nickname, + size_t nickname_len); + +/** + * Parse @p username as @c <guest_prefix><1-25> (case-insensitive prefix). + * Writes slot to @p slot_out. Returns 1 on match, else 0. + */ +int hybbx_guest_slot_from_username(const char *guest_prefix, + const char *username, + unsigned *slot_out); + +struct hybbx_user_record; + +/** Fill @p out with an ephemeral guest record for @p slot (1 … 25). */ +void hybbx_guest_fill_record(const char *guest_prefix, + unsigned slot, + struct hybbx_user_record *out); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_AUTH_H */ diff --git a/include/hybbx/ax25.h b/include/hybbx/ax25.h new file mode 100644 index 0000000..753de15 --- /dev/null +++ b/include/hybbx/ax25.h @@ -0,0 +1,68 @@ +#ifndef HYBBX_AX25_H +#define HYBBX_AX25_H + +#include "hybbx/types.h" + +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define HYBBX_AX25_CALL_MAX 6 +#define HYBBX_AX25_SSID_MAX 15 +#define HYBBX_AX25_ADDR_ENCODED 7 +#define HYBBX_AX25_MAX_DIGI 8 +#define HYBBX_AX25_FRAME_MAX 330 +#define HYBBX_AX25_PID_NOL3 0xF0u +#define HYBBX_AX25_CTRL_UI 0x03u + +typedef struct hybbx_ax25_address { + char call[HYBBX_AX25_CALL_MAX + 1]; + unsigned ssid; +} hybbx_ax25_address_t; + +typedef struct hybbx_ax25_path { + hybbx_ax25_address_t dest; + hybbx_ax25_address_t source; + hybbx_ax25_address_t digi[HYBBX_AX25_MAX_DIGI]; + unsigned digi_count; +} hybbx_ax25_path_t; + +/** + * Parse "CALL" or "CALL-SSID" into @p out. + */ +hybbx_result_t hybbx_ax25_address_parse(const char *text, + hybbx_ax25_address_t *out); + +/** + * Encode one AX.25 address field (7 bytes). + * @p last non-zero on the final address field before control byte. + */ +void hybbx_ax25_encode_address(uint8_t *out, const hybbx_ax25_address_t *addr, + int last); + +/** CRC-CCITT (0x1021), init 0xFFFF — AX.25 FCS. */ +uint16_t hybbx_ax25_crc(const uint8_t *data, size_t len); + +/** + * Build an AX.25 UI frame (PID 0xF0) into @p out. + * @return frame length or 0 on error. + */ +size_t hybbx_ax25_build_ui(const hybbx_ax25_path_t *path, + const uint8_t *payload, size_t payload_len, + uint8_t *out, size_t out_cap); + +/** + * Parse a UI frame. Returns payload length or 0 if not a UI frame / invalid FCS. + */ +size_t hybbx_ax25_parse_ui(const uint8_t *frame, size_t frame_len, + hybbx_ax25_path_t *path, + uint8_t *payload, size_t payload_cap); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_AX25_H */ diff --git a/include/hybbx/bandwidth_policy.h b/include/hybbx/bandwidth_policy.h new file mode 100644 index 0000000..6c12296 --- /dev/null +++ b/include/hybbx/bandwidth_policy.h @@ -0,0 +1,35 @@ +#ifndef HYBBX_BANDWIDTH_POLICY_H +#define HYBBX_BANDWIDTH_POLICY_H + +/** + * Per-user bandwidth limits when a low-bandwidth circuit link is under pressure. + * Circuit (secondary) sessions are never targeted. Among users, AX.25 sessions + * are sacrificed before full-duplex TCP/telnet; within each class, newest first. + */ + +#include "hybbx/circuit_balance.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define HYBBX_BANDWIDTH_DISCONNECT_MSG "You are disconnected by bandwidth limits." + +struct hybbx_service; + +/** Logged-in telnet and packet_radio users subject to limits (not circuit links). */ +unsigned hybbx_bandwidth_policy_user_count(struct hybbx_service *service); + +/** + * Apply pause / break / cancel / resume to online users (AX.25 before TCP, then LIFO). + * Circuit (secondary) sessions are never targeted. Returns users affected. + */ +unsigned hybbx_bandwidth_policy_apply(struct hybbx_service *service, + hybbx_circuit_balance_action_t action); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_BANDWIDTH_POLICY_H */ diff --git a/include/hybbx/baycom.h b/include/hybbx/baycom.h new file mode 100644 index 0000000..a351e55 --- /dev/null +++ b/include/hybbx/baycom.h @@ -0,0 +1,82 @@ +#ifndef HYBBX_BAYCOM_H +#define HYBBX_BAYCOM_H + +/** + * BayCom PR-Stack link adapter — Linux kernel HDLC modem (SER12 / PAR96 / EPP) + * or serial KISS firmware path. + * + * Native PR-Stack L2 uses Thomas Sailer kernel drivers (baycom_ser_fdx, + * baycom_ser_hdx, baycom_par, baycom_epp) and hdlcdrv channel access. + * See docs/BAYCOM.md. + */ + +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Default Linux baycom_ser_fdx netdev (SER12 fullduplex). */ +#define HYBBX_BAYCOM_DEFAULT_INTERFACE "bcsf0" + +/** Default SER12 UART I/O base (COM1). */ +#define HYBBX_BAYCOM_DEFAULT_IOBASE 0x3f8u + +/** Default SER12 IRQ (COM1). */ +#define HYBBX_BAYCOM_DEFAULT_IRQ 4u + +/** Default SER12 on-air baud (1200 AFSK). */ +#define HYBBX_BAYCOM_DEFAULT_RADIO_BAUD 1200u + +typedef enum hybbx_baycom_backend { + HYBBX_BAYCOM_BACKEND_KERNEL = 1, + HYBBX_BAYCOM_BACKEND_KISS = 2, +} hybbx_baycom_backend_t; + +typedef enum hybbx_baycom_modem_mode { + HYBBX_BAYCOM_MODE_SER12 = 1, + HYBBX_BAYCOM_MODE_SER12_SOFTDCD, + HYBBX_BAYCOM_MODE_SER12_INV, + HYBBX_BAYCOM_MODE_SER12HDX, + HYBBX_BAYCOM_MODE_PAR96, + HYBBX_BAYCOM_MODE_PAR96_SOFTDCD, + HYBBX_BAYCOM_MODE_EPP, +} hybbx_baycom_modem_mode_t; + +typedef struct hybbx_baycom_config { + hybbx_baycom_backend_t backend; + hybbx_baycom_modem_mode_t mode; + char *interface; + char *kernel_module; + unsigned iobase; + unsigned irq; + unsigned radio_baud; + int kernel_autoload; + unsigned txdelay; + unsigned txtail; + unsigned slot; + unsigned persist; + int full_duplex; + char *device; + unsigned serial_baud; + char *mycall; + char *circuit_host; + unsigned circuit_port; + char *link_id; + char *link_password; + char *link_role; + char *frequency_mhz; +} hybbx_baycom_config_t; + +hybbx_result_t hybbx_baycom_config_parse(const char *config, + hybbx_baycom_config_t *out); +void hybbx_baycom_config_free(hybbx_baycom_config_t *config); + +const char *hybbx_baycom_modem_mode_name(hybbx_baycom_modem_mode_t mode); +const char *hybbx_baycom_backend_name(hybbx_baycom_backend_t backend); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_BAYCOM_H */ diff --git a/include/hybbx/broadcast.h b/include/hybbx/broadcast.h new file mode 100644 index 0000000..a4002b7 --- /dev/null +++ b/include/hybbx/broadcast.h @@ -0,0 +1,110 @@ +#ifndef HYBBX_BROADCAST_H +#define HYBBX_BROADCAST_H + +/** + * HyBBX broadcast — Main instance only. + * + * User command `/broadcast` (alias `/announce`): instant `+++` message to every + * online local user session on this Main (telnet/SSH/WebSocket). + * Format: `+++ <from>: <message>` (manual Sysop/grant announce). + * Automatic system notices use `***` instead. + * + * `/broadcast ax25`: manual instant RF beacon — ax25_auto_message to each + * qualifying packet-radio link sequentially (no custom RF text). + * + * INI `[broadcast]` ax25_auto: periodic AX.25 QST UI beacon over HBX to + * Secondary extenders (low-bandwidth + half-duplex links). Separate from + * `/broadcast`; not a user command. One sequential cycle per interval (min + * 900 s); 180 s own-channel idle before each link TX; 180 s between links; + * per-link min 900 s. Busy channel defers until idle. + */ + +#include "hybbx/ax25.h" +#include "hybbx/config.h" +#include "hybbx/limits.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define HYBBX_AX25_FREQUENCY_MAX 40u +/** Local Main announce message cap. */ +#define HYBBX_BROADCAST_MESSAGE_MAX 240u + +#define HYBBX_BROADCAST_AUTO_MESSAGE_DEFAULT "Broadcast: @service@ online" + +typedef struct hybbx_ax25_frequency_table { + unsigned count; + double mhz[HYBBX_AX25_FREQUENCY_MAX]; + char labels[HYBBX_AX25_FREQUENCY_MAX][32]; +} hybbx_ax25_frequency_table_t; + +typedef struct hybbx_broadcast_config { + int enabled; + int ax25_enabled; + int ax25_auto; + unsigned ax25_auto_interval_sec; + /** Ignored (legacy INI); links always send sequentially with link gap. */ + unsigned ax25_auto_stagger_sec; + char ax25_mycall[HYBBX_AX25_CALL_MAX + 1]; + char ax25_dest[HYBBX_AX25_CALL_MAX + 1]; + char ax25_auto_message[HYBBX_BROADCAST_AX25_MESSAGE_MAX + 1]; + hybbx_ax25_frequency_table_t frequencies; +} hybbx_broadcast_config_t; + +struct hybbx_service; +struct hybbx_session; + +void hybbx_ax25_frequency_table_clear(hybbx_ax25_frequency_table_t *table); + +/** Load `[ax25]` frequency1 … frequencyN (MHz) and optional labels. */ +void hybbx_ax25_frequency_apply(hybbx_ax25_frequency_table_t *table, + const hybbx_config_t *config); + +/** Compare two MHz values (tolerance for INI rounding). */ +int hybbx_ax25_frequency_match(double a_mhz, double b_mhz); + +void hybbx_broadcast_config_defaults(hybbx_broadcast_config_t *cfg); + +/** Load `[broadcast]` + `[ax25]` from INI. */ +void hybbx_broadcast_config_apply(hybbx_broadcast_config_t *cfg, + const hybbx_config_t *config); + +const hybbx_broadcast_config_t *hybbx_service_get_broadcast( + const struct hybbx_service *service); + +/** + * Send @p message to every online local user on this Main (Sysop `/broadcast`). + */ +hybbx_result_t hybbx_broadcast_announce(struct hybbx_service *service, + struct hybbx_session *from, + const char *message); + +/** + * Manual AX.25 beacon (Sysop `/broadcast ax25`): INI ax25_auto_message to each + * qualifying link immediately, one after another. No custom RF text. + */ +hybbx_result_t hybbx_broadcast_ax25_manual(struct hybbx_service *service); + +/** + * AX.25 auto-beacon over HBX (INI ax25_auto only). + * Link must be low-bandwidth and half-duplex (QoS). + */ +hybbx_result_t hybbx_broadcast_ax25(struct hybbx_service *service, + double frequency_mhz, + const char *message); + +/** + * Periodic AX.25 auto-beacon (call once per second from Main service loop). + */ +void hybbx_broadcast_ax25_tick(struct hybbx_service *service); + +/** Abort an in-flight sequential AX.25 cycle (hub stop / shutdown). */ +void hybbx_broadcast_ax25_seq_cancel(void); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_BROADCAST_H */ diff --git a/include/hybbx/chat.h b/include/hybbx/chat.h new file mode 100644 index 0000000..4f03cb4 --- /dev/null +++ b/include/hybbx/chat.h @@ -0,0 +1,82 @@ +#ifndef HYBBX_CHAT_H +#define HYBBX_CHAT_H + +#include "hybbx/config.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct hybbx_service; +struct hybbx_session; + +/** Hard limit on chat channels (also the default configured count). */ +#define HYBBX_CHAT_CHANNEL_MAX 10u + +#define HYBBX_CHAT_CHANNEL_NAME_MAX 32 + +#define HYBBX_DEFAULT_CHAT_CHANNELS HYBBX_CHAT_CHANNEL_MAX + +/** Maximum characters per chat message line (fits 80-column wrap). */ +#define HYBBX_CHAT_MESSAGE_MAX 72u + +typedef struct hybbx_chat_config { + /** Active channels (1 … @ref HYBBX_CHAT_CHANNEL_MAX). */ + unsigned channel_count; + /** Maximum characters per chat message line. */ + unsigned message_max; + /** + * Channel names (index 0 = channel 1). Default: Channel1 … Channel10. + * Override with [chat] channel1 … channel10 in INI. + */ + char names[HYBBX_CHAT_CHANNEL_MAX][HYBBX_CHAT_CHANNEL_NAME_MAX]; +} hybbx_chat_config_t; + +void hybbx_chat_config_defaults(hybbx_chat_config_t *chat); + +/** Load `[chat]` settings from an INI file. */ +void hybbx_chat_config_apply(hybbx_chat_config_t *chat, + const hybbx_config_t *config); + +const hybbx_chat_config_t *hybbx_service_get_chat(const struct hybbx_service *service); + +/** Return channel name for @p channel_index (1-based), or NULL when invalid. */ +const char *hybbx_chat_channel_name(const hybbx_chat_config_t *chat, + unsigned channel_index); + +/** + * Resolve @p spec as channel number (1-based) or name (case-insensitive). + * @p out_index receives 1-based channel index on success. + */ +hybbx_result_t hybbx_chat_resolve_channel(const hybbx_chat_config_t *chat, + const char *spec, + unsigned *out_index); + +/** List configured channels on @p session. */ +void hybbx_chat_list_channels(struct hybbx_session *session, + const hybbx_chat_config_t *chat); + +/** Broadcast a chat line within the sender's channel. + * Sender sees "ME: …"; others see "<username>: …". + */ +hybbx_result_t hybbx_chat_post(struct hybbx_service *service, + struct hybbx_session *from, + const char *message); + +/** List display names in the requester's current chat channel only. */ +void hybbx_chat_show_channel(struct hybbx_service *service, + struct hybbx_session *session); + +/** + * List all users in public chat channels as nick@ChannelN, sorted by channel, + * wrapped at 80 columns (conference sessions excluded). + */ +void hybbx_chat_show_all(struct hybbx_service *service, + struct hybbx_session *session); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_CHAT_H */ diff --git a/include/hybbx/circuit.h b/include/hybbx/circuit.h new file mode 100644 index 0000000..c732602 --- /dev/null +++ b/include/hybbx/circuit.h @@ -0,0 +1,119 @@ +#ifndef HYBBX_CIRCUIT_H +#define HYBBX_CIRCUIT_H + +/** + * HBX — Hybrid Bridge eXchange (v1). + * + * HyBBX internal framed protocol on the circuit TCP hub. Multiplexes + * link-layer payloads (AX.25 and future stacks) and application streams + * (terminal, proxymail, proxychat) between HyBBX processes. Header magic: + * `H` `B` `X`. Transport is internal TCP/IPv4+IPv6 only — the application + * core never sees KISS, AX.25 on-air framing, or serial. + * + * Short form in docs: **HBX** or **HBX/Circuit** — Circuit = TCP hub + * (:7323), HBX = framing on top. + */ + +#include "hybbx/ax25.h" +#include "hybbx/types.h" + +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define HYBBX_CIRCUIT_MAGIC_0 'H' +#define HYBBX_CIRCUIT_MAGIC_1 'B' +#define HYBBX_CIRCUIT_MAGIC_2 'X' +#define HYBBX_CIRCUIT_VERSION 0x01u + +#define HYBBX_CIRCUIT_HEADER_SIZE 12u +#define HYBBX_CIRCUIT_MAX_PAYLOAD 4096u +#define HYBBX_CIRCUIT_MAX_FRAME (HYBBX_CIRCUIT_HEADER_SIZE + HYBBX_CIRCUIT_MAX_PAYLOAD) + +/** Default internal circuit TCP port (loopback). */ +#define HYBBX_CIRCUIT_DEFAULT_PORT 7323u + +/** + * Protocol identifiers — extensible for future link stacks. + * Values 0x01..0x7F: link-layer payloads; 0x80..0xFF: application streams. + */ +typedef enum hybbx_circuit_proto { + HYBBX_CIRCUIT_PROTO_AX25 = 0x01, /**< Raw AX.25 frame incl. FCS */ + HYBBX_CIRCUIT_PROTO_AX25_UI = 0x02, /**< Masked UI: path + payload */ + HYBBX_CIRCUIT_PROTO_LINK_AUTH = 0x03, /**< Edge link password auth (no ping) */ + HYBBX_CIRCUIT_PROTO_LINK_AUTH_ACK = 0x04,/**< Central auth response */ + HYBBX_CIRCUIT_PROTO_FLOW_CTRL = 0x05, /**< Load-balance pause/break/cancel */ + HYBBX_CIRCUIT_PROTO_TERMINAL = 0x10, /**< BBX / terminal byte stream */ + HYBBX_CIRCUIT_PROTO_PROXY_MAIL = 0x80, /**< Inter-Main proxymail (service only) */ + HYBBX_CIRCUIT_PROTO_PROXY_CHAT = 0x81, /**< Inter-Main proxychat (service only) */ + HYBBX_CIRCUIT_PROTO_RESERVED_APRS = 0x20, + HYBBX_CIRCUIT_PROTO_RESERVED_NETROM = 0x21 +} hybbx_circuit_proto_t; + +typedef enum hybbx_circuit_flags { + HYBBX_CIRCUIT_FLAG_NONE = 0x0000, + HYBBX_CIRCUIT_FLAG_RX = 0x0001, /**< Frame originated on RF RX */ + HYBBX_CIRCUIT_FLAG_TX = 0x0002, /**< Frame destined for RF TX */ + HYBBX_CIRCUIT_FLAG_PATH = 0x0004, /**< AX.25_UI payload carries path */ + HYBBX_CIRCUIT_FLAG_G3RUH_FSK = 0x0008 /**< G3RUH 9600 FSK on-air (vs AFSK) */ +} hybbx_circuit_flags_t; + +typedef void (*hybbx_circuit_frame_cb)(hybbx_circuit_proto_t proto, + uint16_t flags, + const uint8_t *payload, size_t len, + void *userdata); + +typedef struct hybbx_circuit_decoder { + uint8_t buf[HYBBX_CIRCUIT_MAX_FRAME]; + size_t len; + size_t need; + int have_header; + hybbx_circuit_proto_t proto; + uint16_t flags; +} hybbx_circuit_decoder_t; + +void hybbx_circuit_decoder_init(hybbx_circuit_decoder_t *dec); + +void hybbx_circuit_decoder_feed(hybbx_circuit_decoder_t *dec, + const uint8_t *data, size_t len, + hybbx_circuit_frame_cb cb, void *userdata); + +size_t hybbx_circuit_encode(hybbx_circuit_proto_t proto, uint16_t flags, + const uint8_t *payload, size_t payload_len, + uint8_t *out, size_t out_cap); + +size_t hybbx_circuit_encode_ax25(const uint8_t *frame, size_t frame_len, + uint16_t flags, + uint8_t *out, size_t out_cap); + +size_t hybbx_circuit_encode_ax25_ui(const hybbx_ax25_path_t *path, + const uint8_t *payload, size_t payload_len, + uint16_t flags, + uint8_t *out, size_t out_cap); + +size_t hybbx_circuit_encode_terminal(const char *data, size_t len, + uint8_t *out, size_t out_cap); + +/** Encode LINK_AUTH or LINK_AUTH_ACK line-oriented payload. */ +size_t hybbx_circuit_encode_link_msg(hybbx_circuit_proto_t proto, + const char *payload, size_t payload_len, + uint8_t *out, size_t out_cap); + +/** + * Unpack AX.25_UI masked payload into path + UI bytes. + * @return UI payload length or 0 on error. + */ +size_t hybbx_circuit_unpack_ax25_ui(const uint8_t *payload, size_t payload_len, + hybbx_ax25_path_t *path, + uint8_t *ui, size_t ui_cap); + +const char *hybbx_circuit_proto_name(hybbx_circuit_proto_t proto); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_CIRCUIT_H */ diff --git a/include/hybbx/circuit_balance.h b/include/hybbx/circuit_balance.h new file mode 100644 index 0000000..80edbb0 --- /dev/null +++ b/include/hybbx/circuit_balance.h @@ -0,0 +1,131 @@ +#ifndef HYBBX_CIRCUIT_BALANCE_H +#define HYBBX_CIRCUIT_BALANCE_H + +/** + * HBX circuit auto load-balancing — pace and stabilize mixed link speeds, + * duplex modes, and sync/async downlink traffic toward edge adapters. + * + * Escalation (low-bandwidth links): pause → break → cancel connection. + * A few seconds of lag on slow links is normal; the balancer auto-stabilizes. + */ + +#include "hybbx/circuit.h" +#include "hybbx/link.h" +#include "hybbx/types.h" + +#include <stddef.h> +#include <stdint.h> +#include <time.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define HYBBX_CIRCUIT_BALANCE_QUEUE_MAX 64u +#define HYBBX_CIRCUIT_BALANCE_DEFAULT_BAUD 115200u +#define HYBBX_CIRCUIT_BALANCE_LOW_BAUD_THRESHOLD 9600u + +typedef enum hybbx_circuit_bandwidth { + HYBBX_CIRCUIT_BW_HIGH = 0, + HYBBX_CIRCUIT_BW_LOW = 1 +} hybbx_circuit_bandwidth_t; + +typedef enum hybbx_circuit_duplex { + HYBBX_CIRCUIT_DUPLEX_UNSET = 0, + HYBBX_CIRCUIT_DUPLEX_HALF = 1, + HYBBX_CIRCUIT_DUPLEX_FULL = 2 +} hybbx_circuit_duplex_t; + +typedef enum hybbx_circuit_balance_action { + HYBBX_CIRCUIT_BAL_NONE = 0, + HYBBX_CIRCUIT_BAL_PAUSE = 1, + HYBBX_CIRCUIT_BAL_BREAK = 2, + HYBBX_CIRCUIT_BAL_CANCEL = 3, + HYBBX_CIRCUIT_BAL_RESUME = 4 +} hybbx_circuit_balance_action_t; + +typedef enum hybbx_circuit_balance_tick_result { + HYBBX_CIRCUIT_BAL_TICK_OK = 0, + HYBBX_CIRCUIT_BAL_TICK_CANCEL_LINK = 1 +} hybbx_circuit_balance_tick_result_t; + +typedef struct hybbx_circuit_balance_config { + int enabled; + unsigned lag_sec; + size_t queue_pause; + size_t queue_break; + size_t queue_cancel; +} hybbx_circuit_balance_config_t; + +typedef struct hybbx_circuit_link_profile { + hybbx_circuit_bandwidth_t bandwidth; + hybbx_circuit_duplex_t duplex; + unsigned baud; + double frequency_mhz; +} hybbx_circuit_link_profile_t; + +typedef struct hybbx_circuit_link_qos { + unsigned baud; + int duplex; + const char *bandwidth; + const char *frequency_mhz; +} hybbx_circuit_link_qos_t; + +typedef hybbx_result_t (*hybbx_circuit_balance_send_fn)(void *ctx, + const uint8_t *frame, + size_t len); + +typedef void (*hybbx_circuit_balance_flow_fn)(void *ctx, + hybbx_circuit_balance_action_t action, + const char *reason); + +typedef struct hybbx_circuit_balance hybbx_circuit_balance_t; + +void hybbx_circuit_balance_config_defaults(hybbx_circuit_balance_config_t *cfg); + +void hybbx_circuit_link_profile_from_auth(const hybbx_link_auth_t *auth, + hybbx_circuit_link_profile_t *out); + +const char *hybbx_circuit_balance_action_name(hybbx_circuit_balance_action_t action); + +hybbx_result_t hybbx_circuit_flow_ctrl_parse(const char *payload, size_t len, + hybbx_circuit_balance_action_t *action_out, + char *reason_out, size_t reason_cap); + +size_t hybbx_circuit_flow_ctrl_format(hybbx_circuit_balance_action_t action, + const char *reason, + char *out, size_t out_cap); + +hybbx_circuit_balance_t *hybbx_circuit_balance_create( + const hybbx_circuit_balance_config_t *cfg); + +void hybbx_circuit_balance_destroy(hybbx_circuit_balance_t *bal); + +void hybbx_circuit_balance_set_profile(hybbx_circuit_balance_t *bal, + const hybbx_circuit_link_profile_t *profile); + +hybbx_circuit_balance_action_t hybbx_circuit_balance_action( + const hybbx_circuit_balance_t *bal); + +size_t hybbx_circuit_balance_queued_bytes(const hybbx_circuit_balance_t *bal); + +/** De-escalate from cancel after user sacrifice — keep secondary link up. */ +void hybbx_circuit_balance_spared_cancel(hybbx_circuit_balance_t *bal); + +hybbx_result_t hybbx_circuit_balance_submit(hybbx_circuit_balance_t *bal, + const uint8_t *frame, size_t len, + hybbx_circuit_balance_send_fn send_fn, + void *send_ctx, + hybbx_circuit_balance_flow_fn flow_fn, + void *flow_ctx); + +hybbx_circuit_balance_tick_result_t hybbx_circuit_balance_tick( + hybbx_circuit_balance_t *bal, unsigned poll_ms, + hybbx_circuit_balance_send_fn send_fn, void *send_ctx, + hybbx_circuit_balance_flow_fn flow_fn, void *flow_ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_CIRCUIT_BALANCE_H */ diff --git a/include/hybbx/circuit_bridge.h b/include/hybbx/circuit_bridge.h new file mode 100644 index 0000000..1eb24b4 --- /dev/null +++ b/include/hybbx/circuit_bridge.h @@ -0,0 +1,46 @@ +#ifndef HYBBX_CIRCUIT_BRIDGE_H +#define HYBBX_CIRCUIT_BRIDGE_H + +/** + * Main-side bridge registry for remote Secondaries — one entry per + * [transport.packet_radioN], [transport.ardopN], or [transport.crdopN] section + * (link metadata; RF runs on the Secondary host). + * Secondaries (edge extenders/repeaters, not telnet users or local Main transports) + * authenticate with matching link_id + link_password over the HBX/TCP circuit hub. + */ + +#include "hybbx/config.h" +#include "hybbx/link.h" +#include "hybbx/limits.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct hybbx_circuit_bridge_entry { + char link_id[HYBBX_LINK_ID_MAX]; + char link_password[128]; + char link_role[HYBBX_LINK_ROLE_MAX]; + double frequency_mhz; +} hybbx_circuit_bridge_entry_t; + +typedef struct hybbx_circuit_bridge_registry { + hybbx_circuit_bridge_entry_t entries[HYBBX_CIRCUIT_MAX_LINKS]; + unsigned count; +} hybbx_circuit_bridge_registry_t; + +void hybbx_circuit_bridge_clear(hybbx_circuit_bridge_registry_t *reg); + +/** Load transport.packet_radioN, transport.ardopN, and transport.crdopN sections. */ +hybbx_result_t hybbx_circuit_bridge_load(hybbx_circuit_bridge_registry_t *reg, + const hybbx_config_t *config); + +const hybbx_circuit_bridge_entry_t *hybbx_circuit_bridge_find( + const hybbx_circuit_bridge_registry_t *reg, const char *link_id); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_CIRCUIT_BRIDGE_H */ diff --git a/include/hybbx/circuit_tcp.h b/include/hybbx/circuit_tcp.h new file mode 100644 index 0000000..900c379 --- /dev/null +++ b/include/hybbx/circuit_tcp.h @@ -0,0 +1,162 @@ +#ifndef HYBBX_CIRCUIT_TCP_H +#define HYBBX_CIRCUIT_TCP_H + +/** + * HBX/Circuit TCP hub on Main (Circuit = TCP listener :7323; HBX = framed + * payloads on that stream). Remote Secondary processes connect via LINK_AUTH. + * Secondaries are separate edge machines (extenders/repeaters), not telnet users + * or local [transport.*] adapters on Main. + * Hub: [circuit] bind/port. Client: hybbx_circuit_link_* + LINK_AUTH. + * All inter-node paths (Secondary, mains_proxy mesh, future relays) use this + * client pattern only — never a direct socket to a remote HyBBX Main. + * Formal name: HBX — Hybrid Bridge eXchange (v1); see docs/TOPOLOGY.md. + */ + +#include "hybbx/circuit.h" +#include "hybbx/circuit_balance.h" +#include "hybbx/circuit_bridge.h" +#include "hybbx/service.h" +#include "hybbx/types.h" +#include "hybbx/limits.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct hybbx_circuit_hub hybbx_circuit_hub_t; + +typedef struct hybbx_circuit_config { + char bind4[64]; + char bind6[64]; + unsigned port; + int ipv4; + int ipv6; + char link_password[128]; + unsigned link_stale_days; + int link_auth; + char data_path[HYBBX_PATH_MAX]; + char config_path[HYBBX_PATH_MAX]; + unsigned max_links; + hybbx_circuit_bridge_registry_t bridge; + hybbx_circuit_balance_config_t balance; +} hybbx_circuit_config_t; + +/** Internal transport plugin used for sessions bridged over the circuit hub. */ +extern const hybbx_transport_plugin_t hybbx_plugin_circuit; + +void hybbx_circuit_config_defaults(hybbx_circuit_config_t *cfg); + +hybbx_circuit_hub_t *hybbx_circuit_hub_create(hybbx_service_t *service); +void hybbx_circuit_hub_destroy(hybbx_circuit_hub_t *hub); + +hybbx_result_t hybbx_circuit_hub_start(hybbx_circuit_hub_t *hub, + const hybbx_circuit_config_t *cfg); + +void hybbx_circuit_hub_stop(hybbx_circuit_hub_t *hub); + +int hybbx_circuit_hub_running(const hybbx_circuit_hub_t *hub); + +unsigned hybbx_circuit_hub_port(const hybbx_circuit_hub_t *hub); + +/** + * Send a pre-encoded HBX frame to the attached link adapter. + */ +hybbx_result_t hybbx_circuit_hub_send_raw(hybbx_circuit_hub_t *hub, + const uint8_t *frame, size_t len); + +hybbx_result_t hybbx_circuit_hub_send_hbx(hybbx_circuit_hub_t *hub, + const uint8_t *frame, size_t len); + +unsigned hybbx_circuit_hub_active_link_count(const hybbx_circuit_hub_t *hub); + +/** + * Send HBX to all active links. @p frequency_mhz 0 = any MHz. + * @p require_broadcast_qos filters to low-bandwidth + half-duplex links. + * Returns OK when at least one link accepted the frame. + */ +hybbx_result_t hybbx_circuit_hub_multicast_hbx(hybbx_circuit_hub_t *hub, + const uint8_t *frame, size_t len, + double frequency_mhz, + int require_broadcast_qos, + unsigned *sent_out); + +/** + * Send HBX to one circuit slot (sequential AX.25 broadcast). + * @p slot_index must be an active broadcast-QoS link when @p require_broadcast_qos. + */ +hybbx_result_t hybbx_circuit_hub_send_hbx_slot(hybbx_circuit_hub_t *hub, + unsigned slot_index, + const uint8_t *frame, size_t len, + int require_broadcast_qos); + +double hybbx_circuit_hub_link_frequency_mhz(const hybbx_circuit_hub_t *hub); + +/** Non-zero when link QoS is low-bandwidth and half-duplex (AX.25 broadcast allowed). */ +int hybbx_circuit_hub_link_broadcast_qos(const hybbx_circuit_hub_t *hub); + +typedef struct hybbx_circuit_broadcast_link { + double frequency_mhz; + unsigned slot_index; + char link_id[HYBBX_LINK_ID_MAX]; +} hybbx_circuit_broadcast_link_t; + +/** + * Active broadcast-QoS links sorted by MHz (then link_id). + * Returns count written to @p out (may be 0). + */ +unsigned hybbx_circuit_hub_broadcast_links(const hybbx_circuit_hub_t *hub, + hybbx_circuit_broadcast_link_t *out, + unsigned out_max); + +void hybbx_circuit_hub_prune_links(hybbx_circuit_hub_t *hub); + +/** Record RF activity on a link adapter (RX or TX). */ +void hybbx_circuit_hub_note_rf_activity(hybbx_circuit_hub_t *hub, + const char *link_id); + +/** Non-zero when @p slot_index has had no RF activity for @p min_idle_sec. */ +int hybbx_circuit_hub_link_band_idle(const hybbx_circuit_hub_t *hub, + unsigned slot_index, + unsigned min_idle_sec); + +/** + * Earliest time a broadcast may TX on @p slot_index (now if already idle). + * Returns (time_t)-1 when the slot is unavailable. + */ +time_t hybbx_circuit_hub_link_band_ready_at(const hybbx_circuit_hub_t *hub, + unsigned slot_index, + unsigned min_idle_sec); + +/** Link adapter: connect to the internal circuit hub (TCP client). */ +hybbx_result_t hybbx_circuit_link_connect(const char *host, unsigned port, + int *out_fd); + +hybbx_result_t hybbx_circuit_link_write(int fd, const uint8_t *frame, + size_t len); + +hybbx_result_t hybbx_circuit_link_read(int fd, uint8_t *buf, size_t buf_len, + size_t *read_len); + +/** + * Send LINK_AUTH HBX frame after TCP connect (link/repeater edge daemon + * toward the centralized daemon). Password only — no heartbeat/ping. + */ +hybbx_result_t hybbx_circuit_link_authenticate(int fd, + const char *password, + const char *role, + const char *id); + +/** + * LINK_AUTH with optional QoS (baud, duplex, bandwidth) for auto load-balancing. + */ +hybbx_result_t hybbx_circuit_link_authenticate_ex(int fd, + const char *password, + const char *role, + const char *id, + const hybbx_circuit_link_qos_t *qos); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_CIRCUIT_TCP_H */ diff --git a/include/hybbx/command.h b/include/hybbx/command.h new file mode 100644 index 0000000..5d8ba5c --- /dev/null +++ b/include/hybbx/command.h @@ -0,0 +1,71 @@ +#ifndef HYBBX_COMMAND_H +#define HYBBX_COMMAND_H + +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct hybbx_service; +struct hybbx_session; + +/** + * Input line routing scope. + * + * HyBBX accepts only lines starting with '/'. Valid forms: + * / or /command /cmd /commands → /help + * /<verb> or / <verb> → command + * /command <verb> or /cmd <verb> → same as / <verb> + * + * - COMMENT: starts with ';' or '#' — ignored (like empty line) + * - LOCAL: anything else — not HyBBX; silently ignored + */ +typedef enum hybbx_command_scope { + HYBBX_CMD_SCOPE_HYBBX = 1, + HYBBX_CMD_SCOPE_LOCAL = 2, + HYBBX_CMD_SCOPE_COMMENT = 3 +} hybbx_command_scope_t; + +typedef struct hybbx_parsed_command { + hybbx_command_scope_t scope; + char *line; + char *verb; + char **argv; + size_t argc; +} hybbx_parsed_command_t; + +/** + * Classify a single input line. + * Returns HYBBX_CMD_SCOPE_HYBBX only when the first non-whitespace + * character is '/'. + */ +hybbx_command_scope_t hybbx_command_classify(const char *line); + +/** Non-zero when @p line is a HyBBX system command (starts with '/'). */ +int hybbx_command_is_hybbx(const char *line); + +/** Non-zero when @p line is a local comment (';' or '#' after whitespace). */ +int hybbx_command_is_comment(const char *line); + +/** + * Parse a trimmed command line into verb and arguments. + * For HyBBX commands the leading '/' is stripped from the verb. + */ +hybbx_result_t hybbx_command_parse(const char *line, + hybbx_parsed_command_t *out); + +void hybbx_command_free(hybbx_parsed_command_t *cmd); + +/** + * Dispatch a parsed HyBBX command (scope must be HYBBX_CMD_SCOPE_HYBBX). + */ +hybbx_result_t hybbx_command_dispatch(struct hybbx_service *service, + struct hybbx_session *session, + const hybbx_parsed_command_t *cmd); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_COMMAND_H */ diff --git a/include/hybbx/commands_registry.h b/include/hybbx/commands_registry.h new file mode 100644 index 0000000..22b1143 --- /dev/null +++ b/include/hybbx/commands_registry.h @@ -0,0 +1,73 @@ +#ifndef HYBBX_COMMANDS_REGISTRY_H +#define HYBBX_COMMANDS_REGISTRY_H + +#include "hybbx/auth.h" +#include "hybbx/limits.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct hybbx_session; + +typedef struct hybbx_command_def { + char verb[HYBBX_CMD_VERB_MAX]; + char group[16]; + hybbx_user_level_t min_level; + hybbx_user_level_t max_level; /* 0 = no upper cap */ + int only_level; /* HYBBX_LEVEL_* when set; else 0 */ + char line1[HYBBX_COMMANDS_HELP_LINE_MAX]; + char line2[HYBBX_COMMANDS_HELP_LINE_MAX]; +} hybbx_command_def_t; + +/** Load areas.yaml then commands.yaml from install root. Call once at service startup. */ +hybbx_result_t hybbx_commands_registry_init(void); + +void hybbx_commands_registry_shutdown(void); + +const hybbx_command_def_t *hybbx_commands_registry_find(const char *verb); + +/** Map alias or topic name to canonical verb; returns @p topic if unknown. */ +const char *hybbx_commands_registry_canonical(const char *topic); + +int hybbx_commands_registry_verb_allowed(hybbx_user_level_t level, + const char *verb); + +int hybbx_commands_registry_help_allowed(hybbx_user_level_t level, + const char *verb); + +int hybbx_commands_registry_may_userchange(hybbx_user_level_t actor, + hybbx_user_level_t target); + +int hybbx_commands_registry_may_userdelete(hybbx_user_level_t actor, + hybbx_user_level_t target); + +int hybbx_commands_registry_may_promote(hybbx_user_level_t actor, + hybbx_user_level_t target, + int target_active, + hybbx_user_level_t new_level); + +int hybbx_commands_registry_may_demote(hybbx_user_level_t actor, + hybbx_user_level_t target); + +int hybbx_commands_registry_may_delete(hybbx_user_level_t actor, + hybbx_user_level_t target); + +void hybbx_commands_registry_show_menu(struct hybbx_session *session); +void hybbx_commands_registry_show_index(struct hybbx_session *session); +void hybbx_commands_registry_show_aliases(struct hybbx_session *session); +void hybbx_commands_registry_show_help(struct hybbx_session *session, + const char *canonical); + +/** + * List line1 for every registry command with min Admin or Sysop + * (the admin set a Sysop may run while staying invisible via /monitor). + */ +void hybbx_commands_registry_show_sysop_cmds(struct hybbx_session *session); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_COMMANDS_REGISTRY_H */ diff --git a/include/hybbx/conference.h b/include/hybbx/conference.h new file mode 100644 index 0000000..53baa27 --- /dev/null +++ b/include/hybbx/conference.h @@ -0,0 +1,69 @@ +#ifndef HYBBX_CONFERENCE_H +#define HYBBX_CONFERENCE_H + +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct hybbx_service; +struct hybbx_session; + +#define HYBBX_CONFERENCE_TOPIC_MAX 32 + +/** Invite rate limit: max invites per target user within the window. */ +#define HYBBX_CONFERENCE_INVITE_MAX_PER_TARGET 2u + +/** Invite rate limit window (seconds). */ +#define HYBBX_CONFERENCE_INVITE_WINDOW_SEC 1800u + +/** + * Send a conference invite: @p topic to @p partner (login or nickname). + * Partner must accept with y/n or yes/no. Max two invites per target per 30 min. + */ +hybbx_result_t hybbx_conference_start(struct hybbx_service *service, + struct hybbx_session *initiator, + const char *topic, + const char *partner); + +/** + * Monitor/sysop meet: invite @p partner with timeout, or @p force join immediately. + * Topic defaults to "monitor". Status lines go to initiator. + */ +hybbx_result_t hybbx_conference_monitor_meet(struct hybbx_service *service, + struct hybbx_session *initiator, + const char *partner, + int force); + +/** Expire timed-out invites (monitor path). Safe to call every session tick. */ +void hybbx_conference_invite_tick(struct hybbx_service *service, + struct hybbx_session *session); + +/** Non-zero when @p session has a pending conference invite to answer. */ +int hybbx_conference_invite_pending(const struct hybbx_session *session); + +/** + * Handle invite reply line (y/n, yes/no). Call for local input while invite pending. + * Returns HYBBX_OK when consumed. + */ +hybbx_result_t hybbx_conference_reply_invite(struct hybbx_service *service, + struct hybbx_session *session, + const char *line); + +/** Clear pending invites involving @p session (disconnect). */ +void hybbx_conference_session_closed(struct hybbx_session *session); + +/** Deliver a line within an active conference (sender must be in conference area). */ +hybbx_result_t hybbx_conference_post(struct hybbx_service *service, + struct hybbx_session *from, + const char *message); + +/** Tear down conference state when leaving the conference area. */ +void hybbx_conference_area_leaving(struct hybbx_session *session); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_CONFERENCE_H */ diff --git a/include/hybbx/config.h b/include/hybbx/config.h new file mode 100644 index 0000000..c2083ab --- /dev/null +++ b/include/hybbx/config.h @@ -0,0 +1,126 @@ +#ifndef HYBBX_CONFIG_H +#define HYBBX_CONFIG_H + +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct hybbx_config_entry { + char *section; + char *key; + char *value; +} hybbx_config_entry_t; + +typedef struct hybbx_config { + hybbx_config_entry_t *entries; + size_t count; +} hybbx_config_t; + +/** Load configuration from an INI file. */ +hybbx_result_t hybbx_config_load(hybbx_config_t *config, const char *path); + +/** Release all memory held by @p config. */ +void hybbx_config_free(hybbx_config_t *config); + +/** + * Return the value for @p key in @p section, or @p default_value if missing. + * Both @p section and @p key are case-sensitive. + */ +const char *hybbx_config_get(const hybbx_config_t *config, + const char *section, + const char *key, + const char *default_value); + +/** Parse HyBBX boolean tokens (yes/no and aliases; see hybbx_parse_bool). */ +int hybbx_config_get_bool(const hybbx_config_t *config, + const char *section, + const char *key, + int default_value); + +/** + * Parse an unsigned decimal integer in [@p min_value, @p max_value]. + * Returns @p default_value when missing or invalid. + */ +unsigned hybbx_config_get_uint(const hybbx_config_t *config, + const char *section, + const char *key, + unsigned default_value, + unsigned min_value, + unsigned max_value); + +/** + * Build a semicolon-separated key=value string for all keys in @p section. + * Caller must free the returned string. + */ +char *hybbx_config_format_section(const hybbx_config_t *config, + const char *section); + +/** + * Format all @c transport.<plugin> and @c transport.<plugin>N + * sections (in file order) into one start string. Multiple sections are + * joined with @ref HYBBX_PACKET_RADIO_INSTANCE_SEP (packet_radio only). + * Caller must free the returned string. + */ +char *hybbx_config_format_transport_sections(const hybbx_config_t *config, + const char *plugin_name); + +/** + * Like @ref hybbx_config_format_transport_sections for @c packet_radio, but + * prepends a `[max25]` key chunk when that INI section exists. + */ +char *hybbx_config_format_packet_radio_start(const hybbx_config_t *config); + +/** Like packet_radio start — prepends `[max25]` keys for baycom transport. */ +char *hybbx_config_format_baycom_start(const hybbx_config_t *config); + +/** + * Prepend @c [max25] keys as a packet_radio start prefix (before instance sep). + * Caller must free the returned string. + */ +char *hybbx_config_prepend_packet_radio_max25(const hybbx_config_t *config, + const char *body); + +/** + * Resolve INI section for a transport plugin: @c transport.<plugin> first, + * else the first @c transport.<plugin><digits> section with keys + * (e.g. @c transport.packet_radio1). Writes the chosen name to @p out_section. + * Returns 1 when a numbered (or legacy) section with keys was found, else 0. + */ +int hybbx_config_resolve_transport_section(const hybbx_config_t *config, + const char *plugin_name, + char *out_section, + size_t out_size); + +typedef void (*hybbx_config_iter_fn)(const char *section, const char *key, + const char *value, void *ctx); + +typedef void (*hybbx_config_section_iter_fn)(const char *section, void *ctx); + +/** Invoke @p fn for every entry in @p config (in file order). */ +void hybbx_config_foreach(const hybbx_config_t *config, + hybbx_config_iter_fn fn, void *ctx); + +/** Invoke @p fn once per unique section name (first occurrence order). */ +void hybbx_config_foreach_section(const hybbx_config_t *config, + hybbx_config_section_iter_fn fn, void *ctx); + +/** Set or replace a key in @p config (in-memory only until saved). */ +hybbx_result_t hybbx_config_set(hybbx_config_t *config, + const char *section, + const char *key, + const char *value); + +/** Remove all keys in @p section from @p config. */ +void hybbx_config_remove_section(hybbx_config_t *config, const char *section); + +/** Write @p config to @p path (INI format). */ +hybbx_result_t hybbx_config_save(const hybbx_config_t *config, + const char *path); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_CONFIG_H */ diff --git a/include/hybbx/crdop.h b/include/hybbx/crdop.h new file mode 100644 index 0000000..be98200 --- /dev/null +++ b/include/hybbx/crdop.h @@ -0,0 +1,44 @@ +#ifndef HYBBX_CRDOP_H +#define HYBBX_CRDOP_H + +/** + * CRDOP — CB Radio Digital Open Protocol (Level 2, experimental). + * + * HyBBX `crdop` plugin: CB host-client bridge to external CRDOPC. + * ARDOP uses the `ardop` plugin with ARDOPC/ardopcf. + * Modem DSP stays outside HyBBX. Profile helpers shared with `ardop`. + */ + +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum hybbx_crdop_radio_profile { + HYBBX_CRDOP_PROFILE_AMATEUR = 0, + HYBBX_CRDOP_PROFILE_CB = 1 +} hybbx_crdop_radio_profile_t; + +/** Parse radio_profile INI value (cb, amateur). */ +hybbx_crdop_radio_profile_t hybbx_crdop_profile_parse(const char *value); + +const char *hybbx_crdop_profile_name(hybbx_crdop_radio_profile_t profile); + +/** Default ARQ bandwidth string for profile when INI omits arq_bandwidth. */ +const char *hybbx_crdop_default_arq_bandwidth(hybbx_crdop_radio_profile_t profile); + +/** Non-zero when bandwidth string is above CB-safe ceiling (1000MAX). */ +int hybbx_crdop_bandwidth_exceeds_cb(const char *arq_bandwidth); + +struct hybbx_ardop_config; + +/** Parse `[transport.crdop]` config (CB defaults). */ +hybbx_result_t hybbx_crdop_config_parse(const char *config, + struct hybbx_ardop_config *out); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_CRDOP_H */ diff --git a/include/hybbx/crypto.h b/include/hybbx/crypto.h new file mode 100644 index 0000000..5411e5e --- /dev/null +++ b/include/hybbx/crypto.h @@ -0,0 +1,101 @@ +#ifndef HYBBX_CRYPTO_H +#define HYBBX_CRYPTO_H + +#include "hybbx/types.h" + +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * HyBBX reversible cryptography. + * + * Defaults use bundled in-tree sources (tinyaes, monocypher). Backends are + * selectable system-wide via `[crypto]` in hybbx.ini when optional OpenSSL or + * libsodium support is enabled at build time (see hybbx/crypto_config.h). + * + * Three modern AEAD schemes: + * 1. AES-256-GCM (NIST, TLS, IPsec) + * 2. XChaCha20-Poly1305 (extended-nonce ChaCha, Monocypher) + * 3. X25519 + XChaCha20-Poly1305 sealed messages (ECIES-style, Monocypher) + * + * Password hashing (one-way) remains in hybbx/password.h (SHA-256 / MD5). + */ + +typedef enum hybbx_crypto_alg { + HYBBX_CRYPTO_AES_256_GCM = 1, + HYBBX_CRYPTO_XCHACHA20_POLY1305 = 2, + HYBBX_CRYPTO_X25519_AEAD = 3 +} hybbx_crypto_alg_t; + +#define HYBBX_CRYPTO_KEY_SIZE 32 +#define HYBBX_CRYPTO_TAG_SIZE 16 +#define HYBBX_CRYPTO_AES_GCM_NONCE 12 +#define HYBBX_CRYPTO_XCHACHA_NONCE 24 +#define HYBBX_CRYPTO_X25519_PUBLIC_KEY 32 +#define HYBBX_CRYPTO_X25519_SECRET_KEY 32 +/** Sealed blob overhead: ephemeral_pk(32) + nonce(24) + tag(16). */ +#define HYBBX_CRYPTO_X25519_SEAL_OVERHEAD 72 + +/** Fill @p buf with system random bytes. */ +hybbx_result_t hybbx_crypto_random(uint8_t *buf, size_t len); + +/** Human-readable cipher name. */ +const char *hybbx_crypto_alg_name(hybbx_crypto_alg_t alg); + +/** Required nonce length for @p alg (0 for X25519 sealed mode). */ +size_t hybbx_crypto_nonce_size(hybbx_crypto_alg_t alg); + +/** + * Symmetric AEAD encrypt (algorithms 1 and 2). + * @p ciphertext length equals @p plaintext_len; authentication tag is separate. + */ +hybbx_result_t hybbx_crypto_encrypt(hybbx_crypto_alg_t alg, + const uint8_t key[HYBBX_CRYPTO_KEY_SIZE], + const uint8_t *nonce, size_t nonce_len, + const uint8_t *aad, size_t aad_len, + const uint8_t *plaintext, + size_t plaintext_len, + uint8_t *ciphertext, + uint8_t tag[HYBBX_CRYPTO_TAG_SIZE]); + +/** Symmetric AEAD decrypt; returns HYBBX_ERR_DENIED when the tag is invalid. */ +hybbx_result_t hybbx_crypto_decrypt(hybbx_crypto_alg_t alg, + const uint8_t key[HYBBX_CRYPTO_KEY_SIZE], + const uint8_t *nonce, size_t nonce_len, + const uint8_t *aad, size_t aad_len, + const uint8_t *ciphertext, + size_t ciphertext_len, + uint8_t *plaintext, + const uint8_t tag[HYBBX_CRYPTO_TAG_SIZE]); + +/** Generate an X25519 key pair (Montgomery curve, RFC 7748). */ +hybbx_result_t hybbx_crypto_x25519_keypair( + uint8_t public_key[HYBBX_CRYPTO_X25519_PUBLIC_KEY], + uint8_t secret_key[HYBBX_CRYPTO_X25519_SECRET_KEY]); + +/** + * Asymmetric seal for @p recipient_pk. + * Output: ephemeral_pk || nonce || ciphertext || tag. + */ +hybbx_result_t hybbx_crypto_x25519_seal( + const uint8_t recipient_pk[HYBBX_CRYPTO_X25519_PUBLIC_KEY], + const uint8_t *aad, size_t aad_len, + const uint8_t *plaintext, size_t plaintext_len, + uint8_t *sealed, size_t *sealed_len, size_t sealed_cap); + +/** Open a sealed message with @p recipient_sk. */ +hybbx_result_t hybbx_crypto_x25519_open( + const uint8_t recipient_sk[HYBBX_CRYPTO_X25519_SECRET_KEY], + const uint8_t *aad, size_t aad_len, + const uint8_t *sealed, size_t sealed_len, + uint8_t *plaintext, size_t *plaintext_len, size_t plaintext_cap); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_CRYPTO_H */ diff --git a/include/hybbx/crypto_config.h b/include/hybbx/crypto_config.h new file mode 100644 index 0000000..ec1f61d --- /dev/null +++ b/include/hybbx/crypto_config.h @@ -0,0 +1,69 @@ +#ifndef HYBBX_CRYPTO_CONFIG_H +#define HYBBX_CRYPTO_CONFIG_H + +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct hybbx_config; + +/** Password hashing backend for new hashes and verification. */ +typedef enum hybbx_password_hash_backend { + HYBBX_PASSWORD_HASH_TINYSHA256 = 1, + HYBBX_PASSWORD_HASH_OPENSSL = 2 +} hybbx_password_hash_backend_t; + +/** AES-256-GCM implementation backend. */ +typedef enum hybbx_aes_gcm_backend { + HYBBX_AES_GCM_TINYAES = 1, + HYBBX_AES_GCM_OPENSSL = 2 +} hybbx_aes_gcm_backend_t; + +/** XChaCha20-Poly1305 implementation backend. */ +typedef enum hybbx_chacha_backend { + HYBBX_CHACHA_MONOCYPHER = 1, + HYBBX_CHACHA_LIBSODIUM = 2 +} hybbx_chacha_backend_t; + +/** X25519 key agreement backend (sealed-box mode). */ +typedef enum hybbx_x25519_backend { + HYBBX_X25519_MONOCYPHER = 1, + HYBBX_X25519_LIBSODIUM = 2 +} hybbx_x25519_backend_t; + +/** OS random bytes source. */ +typedef enum hybbx_random_backend { + HYBBX_RANDOM_SYSTEM = 1, + HYBBX_RANDOM_OPENSSL = 2 +} hybbx_random_backend_t; + +typedef struct hybbx_crypto_config { + hybbx_password_hash_backend_t password_hash; + hybbx_aes_gcm_backend_t aes_gcm; + hybbx_chacha_backend_t chacha; + hybbx_x25519_backend_t x25519; + hybbx_random_backend_t random; +} hybbx_crypto_config_t; + +/** Bundled defaults: tinysha256, tinyaes, monocypher, system random. */ +void hybbx_crypto_config_defaults(hybbx_crypto_config_t *cfg); + +/** Load `[crypto]` from @p config (missing keys keep defaults). */ +void hybbx_crypto_config_apply(const struct hybbx_config *config); + +/** Active system-wide crypto settings (defaults until apply is called). */ +const hybbx_crypto_config_t *hybbx_crypto_config_get(void); + +const char *hybbx_password_hash_backend_name(hybbx_password_hash_backend_t b); +const char *hybbx_aes_gcm_backend_name(hybbx_aes_gcm_backend_t b); +const char *hybbx_chacha_backend_name(hybbx_chacha_backend_t b); +const char *hybbx_x25519_backend_name(hybbx_x25519_backend_t b); +const char *hybbx_random_backend_name(hybbx_random_backend_t b); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_CRYPTO_CONFIG_H */ diff --git a/include/hybbx/daemon_wrap.h b/include/hybbx/daemon_wrap.h new file mode 100644 index 0000000..0144e5c --- /dev/null +++ b/include/hybbx/daemon_wrap.h @@ -0,0 +1,36 @@ +#ifndef HYBBX_DAEMON_WRAP_H +#define HYBBX_DAEMON_WRAP_H + +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define HYBBX_DAEMON_DEFAULT_SESSION "hybbxd" +#define HYBBX_DAEMON_WRAP_ENV "HYBBXD_WRAPPED" + +typedef struct hybbx_daemon_launch_opts { + int foreground; + int attach; + int use_screen; + int use_tmux; + char session[32]; +} hybbx_daemon_launch_opts_t; + +void hybbx_daemon_launch_opts_defaults(hybbx_daemon_launch_opts_t *opts); + +/** + * Handle --screen / --tmux / --attach before the service loop. + * Spawns or attaches via GNU screen or tmux when requested. + * Returns HYBBX_OK to continue a foreground run; otherwise exits the process. + */ +hybbx_result_t hybbx_daemon_apply_launch_opts(const hybbx_daemon_launch_opts_t *opts, + const char *binary_path, + int argc, char **argv); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_DAEMON_WRAP_H */ diff --git a/include/hybbx/hybbx.h b/include/hybbx/hybbx.h new file mode 100644 index 0000000..e086c2a --- /dev/null +++ b/include/hybbx/hybbx.h @@ -0,0 +1,10 @@ +#ifndef HYBBX_H +#define HYBBX_H + +#define HYBBX_VERSION_MAJOR 2 +#define HYBBX_VERSION_MINOR 8 +#define HYBBX_VERSION_PATCH 0 + +#define HYBBX_VERSION_STRING "2.8.0" + +#endif /* HYBBX_H */ diff --git a/include/hybbx/instance.h b/include/hybbx/instance.h new file mode 100644 index 0000000..9ed07d3 --- /dev/null +++ b/include/hybbx/instance.h @@ -0,0 +1,61 @@ +#ifndef HYBBX_INSTANCE_H +#define HYBBX_INSTANCE_H + +#include "hybbx/networks.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Hardcoded network layout instance (compile-time per binary). + * + * hybbxd — Main (local BBX; WebSocket user path; hub + mains_proxy) + * hybbxsd — Secondary (other-Main peers via mains_proxy; no user BBX) + * hybbxpd — Proxy (hybrid bridge; no WebSocket; no in-process AX.25) + * + * SSoT: vault projects/hybbx/2026-07-16-network-layout-instances.md + */ +typedef enum hybbx_instance_role { + HYBBX_INSTANCE_MAIN = 1, + HYBBX_INSTANCE_SECONDARY = 2, + HYBBX_INSTANCE_PROXY = 3 +} hybbx_instance_role_t; + +hybbx_instance_role_t hybbx_instance_role(void); + +/** + * Standalone Main (hybbxd + local RF on one host). + * Set from INI `[instance] standalone=yes` or auto when Main INI enables local RF. + */ +void hybbx_instance_set_standalone(int enabled); +int hybbx_instance_standalone(void); + +/** "hybbxd" / "hybbxsd" / "hybbxpd" */ +const char *hybbx_instance_binary_name(void); + +/** Short role label: "Main" / "Secondary" / "Proxy". */ +const char *hybbx_instance_role_name(void); + +/** Non-zero when this instance runs local user BBX (login/mail/chat). */ +int hybbx_instance_offers_user_bbx(void); + +/** + * Apply hard layout rules to @p networks (force/clear flags). + * Logs warnings when INI requested a forbidden combination. + * Returns HYBBX_OK, or HYBBX_ERR_INVALID when the role cannot start safely. + */ +hybbx_result_t hybbx_networks_enforce_instance(hybbx_networks_config_t *networks); + +/** + * Non-zero when @p plugin_name may be registered/started for this instance. + * Complements [networks] wanted checks. + */ +int hybbx_instance_plugin_allowed(const char *plugin_name); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_INSTANCE_H */ diff --git a/include/hybbx/kiss.h b/include/hybbx/kiss.h new file mode 100644 index 0000000..9cd378d --- /dev/null +++ b/include/hybbx/kiss.h @@ -0,0 +1,61 @@ +#ifndef HYBBX_KISS_H +#define HYBBX_KISS_H + +#include "hybbx/types.h" + +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define HYBBX_KISS_FEND 0xC0u +#define HYBBX_KISS_FESC 0xDBu +#define HYBBX_KISS_TFEND 0xDCu +#define HYBBX_KISS_TFESC 0xDDu + +typedef enum hybbx_kiss_command { + HYBBX_KISS_CMD_DATA = 0x00, + HYBBX_KISS_CMD_TXDELAY = 0x01, + HYBBX_KISS_CMD_PERSIST = 0x02, + HYBBX_KISS_CMD_SLOTTIME = 0x03, + HYBBX_KISS_CMD_TXTAIL = 0x04, + HYBBX_KISS_CMD_FULLDUPLEX = 0x05, + HYBBX_KISS_CMD_SETHARDWARE = 0x06 +} hybbx_kiss_command_t; + +#define HYBBX_KISS_MAX_FRAME 2048 + +typedef void (*hybbx_kiss_frame_cb)(uint8_t port, const uint8_t *frame, + size_t len, void *userdata); + +typedef struct hybbx_kiss_decoder { + uint8_t buf[HYBBX_KISS_MAX_FRAME]; + size_t len; + int in_frame; + int escape; +} hybbx_kiss_decoder_t; + +void hybbx_kiss_decoder_init(hybbx_kiss_decoder_t *dec); + +/** + * Feed raw serial bytes; complete KISS frames invoke @p cb. + */ +void hybbx_kiss_decoder_feed(hybbx_kiss_decoder_t *dec, + const uint8_t *data, size_t len, + hybbx_kiss_frame_cb cb, void *userdata); + +/** + * Encode a KISS frame into @p out (must hold at least @p out_cap bytes). + * Returns encoded length or 0 on error. + */ +size_t hybbx_kiss_encode(uint8_t port, hybbx_kiss_command_t cmd, + const uint8_t *payload, size_t payload_len, + uint8_t *out, size_t out_cap); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_KISS_H */ diff --git a/include/hybbx/limits.h b/include/hybbx/limits.h new file mode 100644 index 0000000..081af1f --- /dev/null +++ b/include/hybbx/limits.h @@ -0,0 +1,136 @@ +#ifndef HYBBX_LIMITS_H +#define HYBBX_LIMITS_H + +#include "hybbx/traffic.h" + +/** INI parser line buffer size. */ +#define HYBBX_CONFIG_LINE_MAX 512 + +/** Maximum path length for files and directories. */ +#define HYBBX_PATH_MAX 512 + +/** Relative paths under the HyBBX install root (see @ref HYBBX_ENV_ROOT). */ +#define HYBBX_DIR_DATA "data" +#define HYBBX_DIR_TEXT "text" +#define HYBBX_DIR_LOGS "logs" +#define HYBBX_FILE_CONFIG "hybbx.ini" +#define HYBBX_FILE_COMMANDS "commands.yaml" +#define HYBBX_FILE_AREAS "areas.yaml" +#define HYBBX_DAEMON_BINARY "hybbxd" + +/** Command registry (share/commands.yaml + share/areas.yaml). */ +#define HYBBX_COMMANDS_MAX 64u +#define HYBBX_AREAS_MAX 12u +#define HYBBX_AREAS_SUB_MAX 4u +#define HYBBX_COMMANDS_VERBS_PER_GROUP 16u +#define HYBBX_COMMANDS_ALIASES_MAX 48u +#define HYBBX_COMMANDS_ALIAS_PER 8u +#define HYBBX_COMMANDS_ALIAS_LINES 8u +#define HYBBX_MENU_LEVELS_MAX 5u +#define HYBBX_MENU_AREAS_PER_LEVEL 12u +#define HYBBX_RIGHTS_TARGET_RULES_MAX 16u +#define HYBBX_RIGHTS_PROMOTE_RULES_MAX 8u +#define HYBBX_RIGHTS_DEMOTE_RULES_MAX 4u +#define HYBBX_COMMANDS_HELP_LINE_MAX 96u +#define HYBBX_COMMANDS_HEADER_MAX 128u + +/** Environment variable: absolute path to the HyBBX install directory. */ +#define HYBBX_ENV_ROOT "HYBBX_ROOT" + +#define HYBBX_CONFIG_SECTION_MAX 128 +#define HYBBX_CONFIG_KEY_MAX 128 +#define HYBBX_CONFIG_VALUE_MAX 256 + +/** Maximum tokens per HyBBX command line (verb + args). */ +#define HYBBX_CMD_TOKEN_MAX 16 + +#define HYBBX_CMD_VERB_MAX 32 + +/** Maximum registered transport plugins. */ +#define HYBBX_MAX_PLUGINS 16 + +/** Maximum length of the global session prompt (empty = no visible prompt). */ +#define HYBBX_PROMPT_MAX 32 + +/** Maximum single allocation for duplicated strings (bytes). */ +#define HYBBX_ALLOC_MAX (256u * 1024u) + +/** Default maximum simultaneous online sessions (guests included). */ +#define HYBBX_DEFAULT_MAX_ONLINE 35u + +/** Maximum concurrent HBX circuit links (secondaries) on Main. */ +#define HYBBX_CIRCUIT_MAX_LINKS 16u + +/** Default [circuit] max_links when not set in INI. */ +#define HYBBX_CIRCUIT_DEFAULT_MAX_LINKS 8u + +/** Maximum concurrent TNC devices in one packet_radio transport. */ +#define HYBBX_PACKET_RADIO_MAX_INSTANCES 8u + +/** Separates multiple INI sections in one packet_radio start config. */ +#define HYBBX_PACKET_RADIO_INSTANCE_SEP '\x1e' + +/** Maximum concurrent BayCom modems in one baycom transport. */ +#define HYBBX_BAYCOM_MAX_INSTANCES 4u + +/** Separates multiple INI sections in one baycom start config. */ +#define HYBBX_BAYCOM_INSTANCE_SEP '\x1e' + +/** Separates multiple INI sections in one mains_proxy start config. */ +#define HYBBX_MAINS_PROXY_INSTANCE_SEP '\x1e' + +/** Default guest session lifetime before auto-disconnect (minutes). */ +#define HYBBX_DEFAULT_GUEST_TIMEOUT_MINUTES 30u + +/** Command history depth for interactive line editors. */ +#define HYBBX_HISTORY_MAX 25u + +/** AX.25 auto-beacon minimum interval (seconds); INI may only increase this. */ +#define HYBBX_BROADCAST_AX25_INTERVAL_MIN_SEC 900u + +/** Per-link minimum between any AX.25 broadcast TX (manual or auto). */ +#define HYBBX_BROADCAST_AX25_LINK_MIN_SEC 900u + +/** RF channel must be idle this long before an auto AX.25 beacon may TX. */ +#define HYBBX_BROADCAST_AX25_BAND_IDLE_SEC 180u + +/** Minimum pause between successive link TX in one broadcast (seconds). */ +#define HYBBX_BROADCAST_AX25_LINK_GAP_SEC 180u + +/** Retry delay when a sequential broadcast step is deferred (band busy, etc.). */ +#define HYBBX_BROADCAST_AX25_DEFER_RETRY_SEC 30u + +/** Default max25d TCP listen port (MAX25 max25d.ini.example). */ +#define HYBBX_MAX25_DEFAULT_PORT 7325u + +/** Default TCP connect timeout for max25d reachability probe (ms). */ +#define HYBBX_MAX25_PROBE_TIMEOUT_MS 3000u + +/** Wait up to this long for max25d when [max25] check=yes (ms). */ +#define HYBBX_MAX25_PROBE_WAIT_MS 120000u + +/** AX.25 on-air payload cap (1200 baud; keep UI frames short). */ +#define HYBBX_BROADCAST_AX25_MESSAGE_MAX 48u + +/** SQLite fallback copy suffix (see `[storage] backup_path`). */ +#define HYBBX_STORAGE_BACKUP_SUFFIX ".flb" + +/** Default seconds between SQLite DB fallback copies. */ +#define HYBBX_STORAGE_BACKUP_INTERVAL_DEFAULT_SEC 300u + +/** [security] defaults — short cool-down bans (seconds). */ +#define HYBBX_SECURITY_DEFAULT_MAXRETRY 5u +#define HYBBX_SECURITY_DEFAULT_FINDTIME_SEC 600u +#define HYBBX_SECURITY_DEFAULT_BANTIME_SEC 600u +/** Abuse (excessive spam/flood) — higher bar than login failures; no ban for normal use. */ +#define HYBBX_SECURITY_DEFAULT_ABUSE_MAXRETRY 30u +#define HYBBX_SECURITY_DEFAULT_ABUSE_FINDTIME_SEC 600u +#define HYBBX_SECURITY_DEFAULT_RATE_LIMIT 30u +#define HYBBX_SECURITY_DEFAULT_RATE_WINDOW_SEC 60u +#define HYBBX_SECURITY_BAN_MAX 256u +#define HYBBX_SECURITY_TRACK_MAX 512u + +/** AX.25 CALL-SSID or HBX link_id (same ban table). */ +#define HYBBX_CALLID_MAX 64u + +#endif /* HYBBX_LIMITS_H */ diff --git a/include/hybbx/link.h b/include/hybbx/link.h new file mode 100644 index 0000000..3bd60d1 --- /dev/null +++ b/include/hybbx/link.h @@ -0,0 +1,86 @@ +#ifndef HYBBX_LINK_H +#define HYBBX_LINK_H + +/** + * HyBBX link/repeater edge daemon registry. + * + * Roles: gateway, digipeater, repeater, link — edge daemons toward centralized + * daemon [circuit]. Password auth only (LINK_AUTH); stale prune link_stale_days. + * INI: [circuit] link_* ; data/links/<id>.ini ; [link.<id>] in hybbx.ini. + */ + +#include "hybbx/types.h" +#include "hybbx/limits.h" + +#include <stddef.h> +#include <time.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define HYBBX_LINK_STALE_DAYS 10u +#define HYBBX_LINK_ID_MAX 64 +#define HYBBX_LINK_ROLE_MAX 32 +#define HYBBX_LINK_CODE_MAX 16 +#define HYBBX_LINK_AUTH_PAYLOAD_MAX 512 + +typedef struct hybbx_link_auth { + char password[128]; + char role[HYBBX_LINK_ROLE_MAX]; + char id[HYBBX_LINK_ID_MAX]; + unsigned baud; + int duplex; + char bandwidth[16]; + char frequency_mhz[16]; +} hybbx_link_auth_t; + +typedef struct hybbx_link_registry { + char dir[HYBBX_PATH_MAX]; + char config_path[HYBBX_PATH_MAX]; + unsigned stale_days; +} hybbx_link_registry_t; + +void hybbx_link_auth_clear(hybbx_link_auth_t *auth); + +/** + * Build line-oriented LINK_AUTH payload (password=, role=, id=, optional + * baud=, duplex=, bandwidth= for circuit load-balancing). + * @return payload length or 0 on error. + */ +size_t hybbx_link_auth_format(const hybbx_link_auth_t *auth, + char *out, size_t out_cap); + +/** Parse LINK_AUTH payload into @p auth. Returns HYBBX_OK on success. */ +hybbx_result_t hybbx_link_auth_parse(const char *payload, size_t len, + hybbx_link_auth_t *auth); + +void hybbx_link_registry_init(hybbx_link_registry_t *reg, + const char *data_dir, + const char *config_path, + unsigned stale_days); + +/** + * Record successful password authentication for @p id. + * Creates or updates @c data/links/<id>.ini and @c [link.<id>] in hybbx.ini. + */ +hybbx_result_t hybbx_link_registry_touch(hybbx_link_registry_t *reg, + const char *id, + const char *role, + char *link_code_out, + size_t link_code_cap); + +/** + * Remove link entries with @c last_seen older than @p stale_days. + * Also drops matching @c [link.*] sections from hybbx.ini when @p config_path set. + */ +hybbx_result_t hybbx_link_registry_prune(hybbx_link_registry_t *reg); + +/** Generate a short link code (uppercase alphanumeric). */ +void hybbx_link_generate_code(char *out, size_t out_cap); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_LINK_H */ diff --git a/include/hybbx/log.h b/include/hybbx/log.h new file mode 100644 index 0000000..aa70b5b --- /dev/null +++ b/include/hybbx/log.h @@ -0,0 +1,64 @@ +#ifndef HYBBX_LOG_H +#define HYBBX_LOG_H + +#include "hybbx/types.h" +#include "hybbx/limits.h" + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * File + console log severity (least to most severe for filtering): + * debug → stats → info → warn (default threshold). + * Messages at or above the configured [log] level are emitted. + */ +typedef enum hybbx_log_level { + HYBBX_LOG_DEBUG = 0, + HYBBX_LOG_STATS = 1, + HYBBX_LOG_INFO = 2, + HYBBX_LOG_WARN = 3 +} hybbx_log_level_t; + +typedef struct hybbx_log_config { + int enabled; + char dir[HYBBX_PATH_MAX]; + hybbx_log_level_t level; +} hybbx_log_config_t; + +void hybbx_log_config_defaults(hybbx_log_config_t *cfg); +void hybbx_log_config_apply(const struct hybbx_config *config); +const hybbx_log_config_t *hybbx_log_config_get(void); + +/** Non-zero when file logging is active. */ +int hybbx_log_enabled(void); + +/** + * Copy absolute path of the current hybbx log file into @p out. + * Returns HYBBX_OK when logging is enabled and a path is available. + */ +hybbx_result_t hybbx_log_current_path(char *out, size_t out_len); + +const char *hybbx_log_level_name(hybbx_log_level_t level); +hybbx_log_level_t hybbx_log_parse_level(const char *value); + +/** Non-zero when @p level would be written (console and file). */ +int hybbx_log_level_visible(hybbx_log_level_t level); + +void hybbx_log_write(hybbx_log_level_t level, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); + +void hybbx_log_shutdown(void); + +#define hybbx_log_debug(...) hybbx_log_write(HYBBX_LOG_DEBUG, __VA_ARGS__) +#define hybbx_log_stats(...) hybbx_log_write(HYBBX_LOG_STATS, __VA_ARGS__) +#define hybbx_log_info(...) hybbx_log_write(HYBBX_LOG_INFO, __VA_ARGS__) +#define hybbx_log_warn(...) hybbx_log_write(HYBBX_LOG_WARN, __VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_LOG_H */ diff --git a/include/hybbx/mail.h b/include/hybbx/mail.h new file mode 100644 index 0000000..f96e1b6 --- /dev/null +++ b/include/hybbx/mail.h @@ -0,0 +1,129 @@ +#ifndef HYBBX_MAIL_H +#define HYBBX_MAIL_H + +#include "hybbx/config.h" +#include "hybbx/storage.h" +#include "hybbx/types.h" + +#include <time.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct hybbx_service; +struct hybbx_session; + +/** Maximum stored messages per user inbox. */ +#define HYBBX_MAIL_MAX_MESSAGES 50u + +/** Subject line limit (80-column profile). */ +#define HYBBX_MAIL_SUBJECT_MAX 72u + +/** Total message body size (all lines). */ +#define HYBBX_MAIL_BODY_MAX 2048u + +/** Default days before recycled mail is permanently removed. */ +#define HYBBX_MAIL_DEFAULT_RECYCLE_DAYS 10u + +typedef struct hybbx_mail_config { + int enabled; + unsigned max_messages; + unsigned subject_max; + unsigned body_max; + unsigned recycle_days; + /** Root mail directory (typically <storage>/mail). */ + char root[512]; +} hybbx_mail_config_t; + +typedef struct hybbx_mail_entry { + uint64_t id; + char from[64]; + char subject[HYBBX_MAIL_SUBJECT_MAX]; + time_t received_at; + int read; +} hybbx_mail_entry_t; + +void hybbx_mail_config_defaults(hybbx_mail_config_t *mail); + +/** Load `[mail]` and set @p mail->root under @p storage_path. */ +void hybbx_mail_config_apply(hybbx_mail_config_t *mail, + const hybbx_config_t *config, + const char *storage_path); + +const hybbx_mail_config_t *hybbx_service_get_mail(const struct hybbx_service *service); + +/** List inbox on @p session (registered users only). */ +void hybbx_mail_list_inbox(struct hybbx_service *service, + struct hybbx_session *session); + +/** + * List inbox rows @p from..@p to (1-based, newest first). + * @p to = 0 means through the last message. + */ +void hybbx_mail_list_inbox_range(struct hybbx_service *service, + struct hybbx_session *session, + unsigned from, unsigned to); + +/** + * After login: if mail arrived since @p since_login, announce the count. + * No output when mail is disabled, the user is a guest, or the count is zero. + * @p since_login 0 means first login (all inbox messages count as new). + */ +void hybbx_mail_announce_since_last_login(struct hybbx_service *service, + struct hybbx_session *session, + time_t since_login); + +/** + * Parse @p spec as @c from-to (e.g. @c 1-15 , @c 5-20 ) or a single index. + * Returns 0 on invalid input. + */ +int hybbx_mail_parse_list_range(const char *spec, + unsigned *from, unsigned *to); + +/** + * Read message by 1-based list index (newest first). + * Marks the message as read. + */ +hybbx_result_t hybbx_mail_read(struct hybbx_service *service, + struct hybbx_session *session, + unsigned list_index); + +/** Move inbox message(s) to recycle (1-based index or range). */ +hybbx_result_t hybbx_mail_delete(struct hybbx_service *service, + struct hybbx_session *session, + unsigned list_index); + +hybbx_result_t hybbx_mail_delete_range(struct hybbx_service *service, + struct hybbx_session *session, + unsigned from, unsigned to); + +/** Permanently remove all messages in the user's recycle bin. */ +hybbx_result_t hybbx_mail_recycle_empty(struct hybbx_service *service, + struct hybbx_session *session); + +/** + * Deliver a message from @p from_user to @p to_user. + * @p to_user must exist and be an active registered account. + */ +hybbx_result_t hybbx_mail_deliver(struct hybbx_service *service, + const char *from_user, + const char *to_user, + const char *subject, + const char *body); + +/** + * Mail active Sysop and Admin when a guest self-registers. + * Body lists every field from `/register` plus assigned account metadata. + * No-op when mail is disabled. Individual delivery failures are ignored. + */ +hybbx_result_t hybbx_mail_notify_staff_registration( + struct hybbx_service *service, + const hybbx_user_registration_t *reg, + const hybbx_user_record_t *user); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_MAIL_H */ diff --git a/include/hybbx/mains_proxy.h b/include/hybbx/mains_proxy.h new file mode 100644 index 0000000..7d91c87 --- /dev/null +++ b/include/hybbx/mains_proxy.h @@ -0,0 +1,120 @@ +#ifndef HYBBX_MAINS_PROXY_H +#define HYBBX_MAINS_PROXY_H + +/** + * Main-to-Main mesh proxy — link HyBBX instances for user services only. + * + * SECURITY: inter-node mesh links MUST attach via HBX/Circuit only + * (hybbx_circuit_link_connect + LINK_AUTH). Never open a raw TCP socket + * to a remote Main or peer process — same rule as Secondary edge links. + * + * Only proxymail and proxychat service payloads cross the mesh. No user + * accounts, rights, or other Main data are transferred. + */ + +#include "hybbx/circuit.h" +#include "hybbx/config.h" +#include "hybbx/link.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define HYBBX_MAINS_PROXY_PEER_ID_MAX 64 +#define HYBBX_MAINS_PROXY_HOST_MAX 256 +#define HYBBX_MAINS_PROXY_MAX_PEERS 8u + +typedef enum hybbx_mains_proxy_wire { + /** IP path carried on HBX circuit frames (not a direct Main socket). */ + HYBBX_MAINS_PROXY_WIRE_CIRCUIT = 0, + HYBBX_MAINS_PROXY_WIRE_AX25, +} hybbx_mains_proxy_wire_t; + +typedef enum hybbx_mains_proxy_duplex { + HYBBX_MAINS_PROXY_DUPLEX_FULL = 0, + HYBBX_MAINS_PROXY_DUPLEX_HALF, +} hybbx_mains_proxy_duplex_t; + +typedef struct hybbx_mains_proxy_peer_config { + char peer_id[HYBBX_MAINS_PROXY_PEER_ID_MAX]; + char circuit_host[HYBBX_MAINS_PROXY_HOST_MAX]; + unsigned circuit_port; + char link_id[HYBBX_LINK_ID_MAX]; + char link_password[128]; + /** @deprecated Ignored for mesh connect — use circuit_host. */ + char host[HYBBX_MAINS_PROXY_HOST_MAX]; + /** @deprecated Ignored for mesh connect — use circuit_port. */ + unsigned port; + hybbx_mains_proxy_wire_t wire; + hybbx_mains_proxy_duplex_t duplex; + int use_secondary; + int enabled; +} hybbx_mains_proxy_peer_config_t; + +typedef struct hybbx_mains_proxy_mesh { + unsigned peer_count; + hybbx_mains_proxy_peer_config_t peers[HYBBX_MAINS_PROXY_MAX_PEERS]; + int running; +} hybbx_mains_proxy_mesh_t; + +struct hybbx_service; + +hybbx_mains_proxy_wire_t hybbx_mains_proxy_wire_parse(const char *value); +const char *hybbx_mains_proxy_wire_name(hybbx_mains_proxy_wire_t wire); + +hybbx_mains_proxy_duplex_t hybbx_mains_proxy_duplex_parse(const char *value); +const char *hybbx_mains_proxy_duplex_name(hybbx_mains_proxy_duplex_t duplex); + +void hybbx_mains_proxy_peer_defaults(hybbx_mains_proxy_peer_config_t *peer); + +/** Parse one `transport.mains_proxy` / `transport.mains_proxyN` section. */ +hybbx_result_t hybbx_mains_proxy_peer_parse(const char *config, + hybbx_mains_proxy_peer_config_t *out); + +void hybbx_mains_proxy_mesh_init(hybbx_mains_proxy_mesh_t *mesh); + +/** + * Register peers and start mesh. Existing mesh state is replaced when called + * again while running. + */ +hybbx_result_t hybbx_mains_proxy_mesh_start(struct hybbx_service *service, + hybbx_mains_proxy_mesh_t *mesh); + +void hybbx_mains_proxy_mesh_stop(hybbx_mains_proxy_mesh_t *mesh); + +/** Periodic mesh I/O. Call from service main loop when running. */ +void hybbx_mains_proxy_mesh_tick(struct hybbx_service *service, + hybbx_mains_proxy_mesh_t *mesh); + +/** Non-zero when the mesh relay is active with at least one live peer link. */ +int hybbx_mains_proxy_mesh_active(void); + +/** + * Deliver proxymail to a configured peer (HBX PROXY_MAIL frame). + * @p from_address and @p to_address use @c user@service form. + */ +hybbx_result_t hybbx_mains_proxy_send_mail(struct hybbx_service *service, + const char *from_address, + const char *to_address, + const char *subject, + const char *body); + +/** Relay a proxychat line to all connected peers (HBX PROXY_CHAT frame). */ +hybbx_result_t hybbx_mains_proxy_send_chat(struct hybbx_service *service, + const char *from_address, + const char *line); + +/** Handle PROXY_MAIL / PROXY_CHAT received on the circuit hub (inbound link). */ +void hybbx_mains_proxy_inbound_frame(struct hybbx_service *service, + hybbx_circuit_proto_t proto, + const uint8_t *payload, size_t len); + +/** Service-loop tick (implemented by plugin when built). */ +void hybbx_mains_proxy_plugin_tick(void); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_MAINS_PROXY_H */ diff --git a/include/hybbx/max25.h b/include/hybbx/max25.h new file mode 100644 index 0000000..5bae5c3 --- /dev/null +++ b/include/hybbx/max25.h @@ -0,0 +1,72 @@ +#ifndef HYBBX_MAX25_H +#define HYBBX_MAX25_H + +#include "hybbx/config.h" +#include "hybbx/limits.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Default max25d TCP listen address. */ +#define HYBBX_MAX25_DEFAULT_HOST "127.0.0.1" + +typedef struct hybbx_max25_config { + int check; + char host[HYBBX_CONFIG_VALUE_MAX]; + unsigned port; + unsigned timeout_ms; +} hybbx_max25_config_t; + +/** Snapshot from max25d M25/1 STATUS (read-only; HyBBX never overrides). */ +typedef struct hybbx_max25_status { + int handshake_ok; + char error[16]; + char voice[16]; + char stack[32]; + char serial[64]; +} hybbx_max25_status_t; + +void hybbx_max25_config_defaults(hybbx_max25_config_t *cfg); + +/** Load `[max25]` from INI. `check` defaults to `yes`. Reporting is MAX25-only. */ +void hybbx_max25_config_apply(hybbx_max25_config_t *cfg, + const hybbx_config_t *config); + +/** Parse a semicolon key=value chunk (`max25_check=…` keys). */ +void hybbx_max25_config_parse_kv(const char *max25_kv, + hybbx_max25_config_t *cfg); + +/** + * When @p config starts with a max25 prefix (before + * @ref HYBBX_PACKET_RADIO_INSTANCE_SEP), parse it and return the remainder. + * When no prefix is present, @p cfg->check is cleared. + */ +const char *hybbx_max25_config_skip_prefix(const char *config, + hybbx_max25_config_t *cfg); + +void hybbx_max25_status_clear(hybbx_max25_status_t *status); + +/** + * TCP connect + M25/1 connect handshake (`OK` + `STATUS …`). + * Returns @ref HYBBX_OK when max25d responds; @p status_out receives + * `error=` / `voice=` / `stack=` / `serial=` as reported by MAX25 (valid or invalid). + * Data-quality rules (3×20s passes, CALLID, min % good) live in max25d only — + * HyBBX accepts STATUS without override; only TCP/handshake failure is fatal. + */ +hybbx_result_t hybbx_max25_probe(const hybbx_max25_config_t *cfg, + hybbx_max25_status_t *status_out); + +/** + * When @p cfg->check is set, block until max25d responds or + * @ref HYBBX_MAX25_PROBE_WAIT_MS elapses. + * Returns @ref HYBBX_OK when max25d is reachable; @ref HYBBX_ERR_IO on timeout. + */ +hybbx_result_t hybbx_max25_wait_ready(const hybbx_max25_config_t *cfg); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_MAX25_H */ diff --git a/include/hybbx/messages.h b/include/hybbx/messages.h new file mode 100644 index 0000000..d1c7345 --- /dev/null +++ b/include/hybbx/messages.h @@ -0,0 +1,58 @@ +#ifndef HYBBX_MESSAGES_H +#define HYBBX_MESSAGES_H + +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct hybbx_session; + +/** Automatic system notices (login, mail, timeouts, conference status, …). */ +#define HYBBX_MSG_PREFIX_SYSTEM "*** " + +/** Sysop / granted manual system-wide notices (`/broadcast`, `/announce`). */ +#define HYBBX_MSG_PREFIX_SYSOP "+++ " + +/** Private directed messages (system or Sysop+granted); v1 half-stub. */ +#define HYBBX_MSG_PREFIX_PRIVATE "### " + +/** + * Format a system line: `*** <body>` + */ +hybbx_result_t hybbx_msg_format_system(char *out, size_t out_len, + const char *body); + +/** + * Format a Sysop/granted system-wide line: `+++ <from>: <body>` + * @p from_label may be NULL → `+++ <body>` + */ +hybbx_result_t hybbx_msg_format_sysop(char *out, size_t out_len, + const char *from_label, + const char *body); + +/** + * Format a private line: `### <from>: <body>` or `### <body>` when @p from_label is NULL. + */ +hybbx_result_t hybbx_msg_format_private(char *out, size_t out_len, + const char *from_label, + const char *body); + +hybbx_result_t hybbx_msg_send_system(struct hybbx_session *session, + const char *body); + +hybbx_result_t hybbx_msg_send_sysop(struct hybbx_session *session, + const char *from_label, + const char *body); + +/** Half-stub: directed private message to one session. */ +hybbx_result_t hybbx_msg_send_private(struct hybbx_session *session, + const char *from_label, + const char *body); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_MESSAGES_H */ diff --git a/include/hybbx/monitor.h b/include/hybbx/monitor.h new file mode 100644 index 0000000..a4d8db5 --- /dev/null +++ b/include/hybbx/monitor.h @@ -0,0 +1,74 @@ +#ifndef HYBBX_MONITOR_H +#define HYBBX_MONITOR_H + +#include "hybbx/limits.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct hybbx_config; +struct hybbx_service; +struct hybbx_session; + +#define HYBBX_MONITOR_ALLOW_MAX 16u +#define HYBBX_MONITOR_ALLOW_NAME_MAX 64u + +typedef struct hybbx_monitor_config { + int enabled; + int follow_hybbx; + int follow_security; + /** When set, Sysop sessions are always hidden from /who and /online. */ + int invisible_sysop; + unsigned invite_timeout_sec; + char allow[HYBBX_MONITOR_ALLOW_MAX][HYBBX_MONITOR_ALLOW_NAME_MAX]; + unsigned allow_count; +} hybbx_monitor_config_t; + +void hybbx_monitor_config_defaults(hybbx_monitor_config_t *cfg); +void hybbx_monitor_config_apply(const struct hybbx_config *config); +const hybbx_monitor_config_t *hybbx_monitor_config_get(void); + +/** Non-zero when [monitor] enabled=yes. */ +int hybbx_monitor_enabled(void); + +/** Non-zero when [monitor] invisible-sysop=yes (Sysop always /who-hidden). */ +int hybbx_monitor_invisible_sysop(void); + +/** Non-zero when @p username is listed in [monitor] allow=. */ +int hybbx_monitor_user_allowed(const char *username); + +/** + * Non-zero when @p session may use /monitor (Sysop/registry OR allow list). + * Caller still runs normal command access for registry min-level. + */ +int hybbx_monitor_session_may_use(const struct hybbx_session *session); + +/** Enable/disable monitor mode on @p session. */ +hybbx_result_t hybbx_monitor_set_active(struct hybbx_session *session, int on); + +int hybbx_monitor_is_active(const struct hybbx_session *session); + +/** + * Push a live line to every session with monitor mode on. + * Prefixes with "[monitor] " for clarity. + */ +void hybbx_monitor_broadcast(struct hybbx_service *service, const char *line); + +/** Format Username@plugin event and push live (always when any monitor on). */ +void hybbx_monitor_event(struct hybbx_service *service, + const char *username, + const char *plugin, + const char *detail); + +/** Poll log file tails (call from service loop). */ +void hybbx_monitor_tick(struct hybbx_service *service); + +void hybbx_monitor_shutdown(void); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_MONITOR_H */ diff --git a/include/hybbx/networks.h b/include/hybbx/networks.h new file mode 100644 index 0000000..ab8e954 --- /dev/null +++ b/include/hybbx/networks.h @@ -0,0 +1,63 @@ +#ifndef HYBBX_NETWORKS_H +#define HYBBX_NETWORKS_H + +#include "hybbx/config.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Connection / link adapter switches from INI `[networks]`. + * + * Static transport depends on instance role (see hybbx/instance.h): + * Main — websocket (always on when built) + * Secondary — mains_proxy + * Proxy — none (optional TCP transports) + */ +typedef struct hybbx_networks_config { + /** Telnet (`transport.telnet`) — not used on Main (WebSocket only). */ + int telnet; + /** AX.25 / packet radio — always forced off (MAX25-Stack owns RF). */ + int ax25; + /** BayCom — always forced off (MAX25-Stack owns RF). */ + int baycom; + /** ARDOP — always forced off (MAX25-Stack owns RF / soft-modem). */ + int ardop; + /** CRDOP — always forced off (MAX25-Stack owns RF / soft-modem). */ + int crdop; + /** SSH transport (`transport.ssh`). */ + int ssh; + /** WebSocket forward-proxy (`transport.websocket`) — Main static path. */ + int websocket; + /** Internal HBX circuit hub / attach. */ + int circuit; + /** Main-to-Main mesh (`transport.mains_proxy`) — Secondary primary. */ + int mains_proxy; +} hybbx_networks_config_t; + +void hybbx_networks_config_defaults(hybbx_networks_config_t *networks); + +/** Load `[networks]` from an INI file. */ +void hybbx_networks_config_apply(hybbx_networks_config_t *networks, + const hybbx_config_t *config); + +/** + * Non-zero when @p plugin_name is the role's static core transport. + * Ignores `[transport.*] enabled` and is started when built. + */ +int hybbx_networks_is_static_transport(const char *plugin_name); + +/** + * Non-zero when transport @p plugin_name should be started for this config. + * Respects instance plugin allow-list and `[networks]` toggles. + */ +int hybbx_networks_transport_wanted(const char *plugin_name, + const hybbx_networks_config_t *networks); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_NETWORKS_H */ diff --git a/include/hybbx/packet_radio.h b/include/hybbx/packet_radio.h new file mode 100644 index 0000000..51f79a0 --- /dev/null +++ b/include/hybbx/packet_radio.h @@ -0,0 +1,57 @@ +#ifndef HYBBX_PACKET_RADIO_H +#define HYBBX_PACKET_RADIO_H + +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Default line speed for packet radio (2400 baud). */ +#define HYBBX_PACKET_RADIO_DEFAULT_BAUD 2400 + +/** Default USB AX.25 TNC/modem device (Linux). */ +#define HYBBX_PACKET_RADIO_DEFAULT_DEVICE_USB "/dev/ttyUSB0" + +#if defined(_WIN32) +#define HYBBX_PACKET_RADIO_DEFAULT_DEVICE_SERIAL "COM1" +#elif defined(__AMIGA__) +#define HYBBX_PACKET_RADIO_DEFAULT_DEVICE_SERIAL "serial.device" +#else +#define HYBBX_PACKET_RADIO_DEFAULT_DEVICE_SERIAL "/dev/ttyS0" +#endif + +typedef enum hybbx_packet_radio_device_type { + HYBBX_PACKET_RADIO_DEVICE_USB = 1, + HYBBX_PACKET_RADIO_DEVICE_SERIAL = 2 +} hybbx_packet_radio_device_type_t; + +struct hybbx_packet_radio_config; + +/** + * Parse a semicolon-separated key=value transport config string. + * Missing keys receive HyBBX defaults (TNC2C, USB, KISS @ 2400 baud). + * Full config type: include hybbx/tnc.h. + */ +hybbx_result_t hybbx_packet_radio_config_parse(const char *config, + struct hybbx_packet_radio_config *out); + +void hybbx_packet_radio_config_free(struct hybbx_packet_radio_config *config); + +/** + * Non-zero when @p config starts a local TNC (device/tnc/protocol/circuit_host). + * Main bridge-registry rows (link_id only) return zero. + */ +int hybbx_packet_radio_section_is_local_edge(const char *config); + +/** + * Return non-zero when @p baud is valid for packet radio configuration. + * HyBBX accepts any positive host baud rate; TNC2C documents 300..38400. + */ +int hybbx_packet_radio_baud_valid(unsigned int baud); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_PACKET_RADIO_H */ diff --git a/include/hybbx/password.h b/include/hybbx/password.h new file mode 100644 index 0000000..cc1b38e --- /dev/null +++ b/include/hybbx/password.h @@ -0,0 +1,53 @@ +#ifndef HYBBX_PASSWORD_H +#define HYBBX_PASSWORD_H + +#include "hybbx/types.h" + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** Stored password prefix for SHA-256 (default, tinysha256). */ +#define HYBBX_PASSWORD_SHA256_PREFIX "{sha256}" +/** Stored password prefix for legacy MD5 (verify-only fallback). */ +#define HYBBX_PASSWORD_MD5_PREFIX "{md5}" + +/** Max stored password field: prefix + 64 hex digits + NUL. */ +#define HYBBX_PASSWORD_STORED_MAX 96 + +/** Non-zero when @p stored is a hashed password (sha256 or md5 prefix). */ +int hybbx_password_is_hashed(const char *stored); + +/** Non-zero when @p stored is plain text and should be upgraded on load. */ +int hybbx_password_is_plain(const char *stored); + +/** + * Hash @p plain into @p out as {sha256}<hex> using the configured backend + * (default: bundled tinysha256; see [crypto] password_hash in hybbx.ini). + */ +hybbx_result_t hybbx_password_hash_sha256(const char *plain, + char *out, + size_t out_size); + +/** Hash @p plain with the configured default algorithm (SHA-256). */ +hybbx_result_t hybbx_password_hash(const char *plain, char *out, size_t out_size); + +/** Return non-zero when @p provided matches @p stored (plain, sha256, or md5). */ +int hybbx_password_match(const char *stored, const char *provided); + +/** + * Fill @p out with a random password of length @p min_len … @p max_len using + * characters a-z and 0-9 only. + */ +hybbx_result_t hybbx_password_generate_alnum(char *out, + size_t out_size, + size_t min_len, + size_t max_len); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_PASSWORD_H */ diff --git a/include/hybbx/plugin.h b/include/hybbx/plugin.h new file mode 100644 index 0000000..ea72a65 --- /dev/null +++ b/include/hybbx/plugin.h @@ -0,0 +1,75 @@ +#ifndef HYBBX_PLUGIN_H +#define HYBBX_PLUGIN_H + +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct hybbx_service; +struct hybbx_session; + +/** + * Transport plugin interface (link adapters / host-client bridges). + * + * HyBBX is plugin-only: session core + plugins. Modems, TNCs, sound-card + * software, ARDOPC, and CRDOPC are external — never embedded in HyBBX. + * + * Core uses TCP/IPv4+IPv6 and HBX internally. Plugins terminate the wire + * to external services (telnet TCP, serial TNC, ARDOP host TCP, …) and + * expose a byte stream to hybbx_session via the write callback. + */ +typedef struct hybbx_transport_plugin { + const char *name; + hybbx_transport_kind_t kind; + unsigned int version; + + /** Called once when the plugin is loaded. */ + hybbx_result_t (*init)(struct hybbx_service *service); + + /** Called once when the plugin is unloaded. */ + void (*shutdown)(void); + + /** + * Start listening / accepting connections on the given bind address. + * @p config is a semicolon-separated key=value string built from the + * transport's INI section (e.g. "bind=0.0.0.0;port=2323"). + */ + hybbx_result_t (*start)(const char *config); + + /** Stop accepting new connections; existing sessions may continue. */ + hybbx_result_t (*stop)(void); + + /** + * Send raw bytes to the connected client for this session. + * NULL → core writes to stdout (development fallback). + */ + hybbx_result_t (*write)(struct hybbx_session *session, + const char *data, size_t len); + +} hybbx_transport_plugin_t; + +/** + * Session callbacks invoked by a transport plugin when a client connects. + */ +typedef struct hybbx_session_ops { + hybbx_result_t (*on_connect)(struct hybbx_session *session, void *userdata); + hybbx_result_t (*on_data)(struct hybbx_session *session, + const uint8_t *data, size_t len, + void *userdata); + void (*on_disconnect)(struct hybbx_session *session, void *userdata); +} hybbx_session_ops_t; + +/** Opaque session handle passed between core and transport plugins. */ +typedef struct hybbx_session { + const hybbx_transport_plugin_t *transport; + void *transport_data; + void *core_data; +} hybbx_session_t; + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_PLUGIN_H */ diff --git a/include/hybbx/posix_time.h b/include/hybbx/posix_time.h new file mode 100644 index 0000000..6790120 --- /dev/null +++ b/include/hybbx/posix_time.h @@ -0,0 +1,21 @@ +#ifndef HYBBX_POSIX_TIME_H +#define HYBBX_POSIX_TIME_H + +/* + * clock_gettime(CLOCK_MONOTONIC) and usleep() on glibc/Linux (GCC 15+ C99). + * Include before <time.h> / <unistd.h> in .c files, or rely on CMake + * _DEFAULT_SOURCE on Linux builds. + */ +#if defined(__linux__) || defined(__GLIBC__) +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE 1 +#endif +#endif + +#include <time.h> + +#if !defined(_WIN32) +#include <unistd.h> +#endif + +#endif /* HYBBX_POSIX_TIME_H */ diff --git a/include/hybbx/privilege.h b/include/hybbx/privilege.h new file mode 100644 index 0000000..df646c8 --- /dev/null +++ b/include/hybbx/privilege.h @@ -0,0 +1,24 @@ +#ifndef HYBBX_PRIVILEGE_H +#define HYBBX_PRIVILEGE_H + +#include "hybbx/config.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Apply [service] user=/group=/uid=/gid= from @p config. + * + * Linux: hybbxd must not keep running as root. When euid==0, @c user is + * required and privileges drop immediately via setuid/setgid. + * Non-root starts are unchanged (optional @c user must match effective uid). + */ +hybbx_result_t hybbx_privilege_apply_from_config(const hybbx_config_t *config); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_PRIVILEGE_H */ diff --git a/include/hybbx/proxychat.h b/include/hybbx/proxychat.h new file mode 100644 index 0000000..74f71f2 --- /dev/null +++ b/include/hybbx/proxychat.h @@ -0,0 +1,40 @@ +#ifndef HYBBX_PROXYCHAT_H +#define HYBBX_PROXYCHAT_H + +/** + * Inter-Main chat via mains_proxy. + * + * Local `/chat` stays on this Main. `/proxychat` is the mains-proxy sub-area + * under `/chat` (or `/chat proxychat`). Only chat lines cross the mesh — no + * user accounts or rights. + */ + +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct hybbx_service; +struct hybbx_session; + +/** Show proxychat area banner. */ +void hybbx_proxychat_show_banner(struct hybbx_session *session); + +/** + * Post a line in proxychat (relay when mesh is active). + */ +hybbx_result_t hybbx_proxychat_post(struct hybbx_service *service, + struct hybbx_session *session, + const char *line); + +/** Deliver a line received from a remote Main. */ +void hybbx_proxychat_receive(struct hybbx_service *service, + const char *from_address, + const char *line); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_PROXYCHAT_H */ diff --git a/include/hybbx/proxymail.h b/include/hybbx/proxymail.h new file mode 100644 index 0000000..123b663 --- /dev/null +++ b/include/hybbx/proxymail.h @@ -0,0 +1,73 @@ +#ifndef HYBBX_PROXYMAIL_H +#define HYBBX_PROXYMAIL_H + +/** + * Inter-Main mail via mains_proxy — separate from local `/mail`. + * + * Local mail uses flat-file or SQL under `[mail]`; proxymail uses flat files + * under `data/proxymail/` on each node. Only service payloads cross the mesh. + */ + +#include "hybbx/mail.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** `username@remote-main-service` address string. */ +#define HYBBX_PROXYMAIL_ADDRESS_MAX 128u +#define HYBBX_PROXYMAIL_SERVICE_NAME_MAX 64u + +struct hybbx_service; +struct hybbx_session; + +/** + * Parse @p address as @c user@remote-service . + * @return 0 when invalid. + */ +int hybbx_proxymail_parse_address(const char *address, + char *user, size_t user_len, + char *remote_service, size_t remote_len); + +/** List proxymail inbox (stub). */ +void hybbx_proxymail_list_inbox(struct hybbx_service *service, + struct hybbx_session *session); + +void hybbx_proxymail_list_inbox_range(struct hybbx_service *service, + struct hybbx_session *session, + unsigned from, unsigned to); + +hybbx_result_t hybbx_proxymail_read(struct hybbx_service *service, + struct hybbx_session *session, + unsigned list_index); + +hybbx_result_t hybbx_proxymail_delete_range(struct hybbx_service *service, + struct hybbx_session *session, + unsigned from, unsigned to); + +hybbx_result_t hybbx_proxymail_recycle_empty(struct hybbx_service *service, + struct hybbx_session *session); + +/** + * Queue outbound proxymail for mesh delivery. + * @p to_address must be @c user@remote-main-service . + */ +hybbx_result_t hybbx_proxymail_deliver(struct hybbx_service *service, + const char *from_user, + const char *to_address, + const char *subject, + const char *body); + +/** Store proxymail received from a remote Main. */ +void hybbx_proxymail_receive(struct hybbx_service *service, + const char *from_address, + const char *to_address, + const char *subject, + const char *body); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_PROXYMAIL_H */ diff --git a/include/hybbx/registry.h b/include/hybbx/registry.h new file mode 100644 index 0000000..51577ee --- /dev/null +++ b/include/hybbx/registry.h @@ -0,0 +1,28 @@ +#ifndef HYBBX_REGISTRY_H +#define HYBBX_REGISTRY_H + +#include "hybbx/plugin.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Register a transport plugin (called at plugin init time). */ +hybbx_result_t hybbx_registry_register(const hybbx_transport_plugin_t *plugin); + +/** Unregister a transport plugin by name. */ +hybbx_result_t hybbx_registry_unregister(const char *name); + +/** Look up a registered plugin by name. Returns NULL if not found. */ +const hybbx_transport_plugin_t *hybbx_registry_find(const char *name); + +/** Iterate over all registered plugins. */ +typedef void (*hybbx_registry_iter_fn)(const hybbx_transport_plugin_t *plugin, + void *userdata); +void hybbx_registry_foreach(hybbx_registry_iter_fn fn, void *userdata); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_REGISTRY_H */ diff --git a/include/hybbx/rf_tx_pace.h b/include/hybbx/rf_tx_pace.h new file mode 100644 index 0000000..f987490 --- /dev/null +++ b/include/hybbx/rf_tx_pace.h @@ -0,0 +1,16 @@ +#ifndef HYBBX_RF_TX_PACE_H +#define HYBBX_RF_TX_PACE_H + +/** Minimum idle time between RF frame sends (HyBBX packet radio). */ +#define HYBBX_RF_TX_MIN_GAP_MS 1500u + +/** Maximum continuous RF send burst (wall clock); new burst after MIN_GAP idle. */ +#define HYBBX_RF_TX_MAX_BURST_MS 15000u + +/** Min gap + max burst (TNC / direct serial). */ +void hybbx_rf_tx_pace(void); + +/** Max burst only — use when max25-bcpr enforces min gap on KISS (baycom plugin). */ +void hybbx_rf_tx_burst_guard(void); + +#endif diff --git a/include/hybbx/security.h b/include/hybbx/security.h new file mode 100644 index 0000000..f4a9476 --- /dev/null +++ b/include/hybbx/security.h @@ -0,0 +1,31 @@ +#ifndef HYBBX_SECURITY_H +#define HYBBX_SECURITY_H + +#include "hybbx/types.h" + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct hybbx_config; + +/** Log file name under the configured logs directory. */ +#define HYBBX_SECURITY_LOG_FILE "security.log" + +void hybbx_security_log_config_apply(const struct hybbx_config *config); +void hybbx_security_log_shutdown(void); + +/** Append one line to security.log (timestamp added). Always enabled when the log directory is available. */ +void hybbx_security_log_write(const char *fmt, ...) + __attribute__((format(printf, 1, 2))); + +/** Absolute path to security.log when the security log is ready. */ +hybbx_result_t hybbx_security_log_current_path(char *out, size_t out_len); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_SECURITY_H */ diff --git a/include/hybbx/security_ban.h b/include/hybbx/security_ban.h new file mode 100644 index 0000000..9e63757 --- /dev/null +++ b/include/hybbx/security_ban.h @@ -0,0 +1,80 @@ +#ifndef HYBBX_SECURITY_BAN_H +#define HYBBX_SECURITY_BAN_H + +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct hybbx_config; + +typedef enum hybbx_ban_backend { + HYBBX_BAN_BACKEND_INTERNAL = 0, + HYBBX_BAN_BACKEND_LOG, + HYBBX_BAN_BACKEND_IPTABLES, + HYBBX_BAN_BACKEND_NFTABLES, + HYBBX_BAN_BACKEND_HOSTS +} hybbx_ban_backend_t; + +void hybbx_security_ban_config_apply(const struct hybbx_config *config); +void hybbx_security_ban_shutdown(void); + +/** Expire bans and prune stale failure/rate records (service loop). */ +void hybbx_security_ban_tick(void); + +/** Non-zero when @p ip is currently banned. */ +int hybbx_security_ban_is_banned(const char *ip); + +/** + * Normalize @p in into @p out (uppercase CALL/CALL-SSID or link_id). + * Returns non-zero when valid. + */ +int hybbx_security_callid_normalize(const char *in, char *out, size_t out_cap); + +/** Non-zero when @p callid is banned (AX.25 callsign or HBX link_id). */ +int hybbx_security_ban_callid_is_banned(const char *callid); + +/** + * Accept gate for HBX link_id / AX.25 source before circuit or RF uplink. + * Returns non-zero when allowed. + */ +int hybbx_security_ban_callid_accept(const char *callid); + +/** + * Accept gate: rate-limit check + ban check. + * Returns non-zero when the connection may proceed. + */ +int hybbx_security_ban_accept(const char *ip); + +/** + * Same as @ref hybbx_security_ban_accept using the peer address of @p fd. + * Allows the connection when the peer address cannot be resolved. + */ +int hybbx_security_ban_accept_fd(int fd); + +/** Record a failed login for @p transport (telnet, ssh, websocket, …). */ +void hybbx_security_ban_login_fail(const char *ip, const char *transport); + +/** Record a failed HBX circuit link authentication (IP). */ +void hybbx_security_ban_link_auth_fail(const char *ip); + +/** Record a failed HBX circuit link authentication (link_id / CALLID). */ +void hybbx_security_ban_link_auth_fail_callid(const char *callid); + +/** + * Record excessive abuse (chat/mail flood, register spam, …). + * Uses @c abuse_maxretry / @c abuse_findtime — not login @c maxretry. + * Normal traffic limits ([chat], [mail], [traffic]) never call this. + */ +void hybbx_security_ban_abuse_report(const char *ip, const char *category); + +/** Same as @ref hybbx_security_ban_abuse_report for @p callid. */ +void hybbx_security_ban_callid_abuse_report(const char *callid, + const char *category); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_SECURITY_BAN_H */ diff --git a/include/hybbx/service.h b/include/hybbx/service.h new file mode 100644 index 0000000..4730b8b --- /dev/null +++ b/include/hybbx/service.h @@ -0,0 +1,174 @@ +#ifndef HYBBX_SERVICE_H +#define HYBBX_SERVICE_H + +/** + * Centralized HyBBX daemon API (`hybbx` binary). + * + * Loads INI ([service], [storage], [auth], [transport.*], [circuit]), starts + * transport plugins and the HBX circuit hub. Edge link/repeater daemons attach + * to [circuit]; see share/hybbx.ini.example and docs/TOPOLOGY.md. + */ + +#include "hybbx/config.h" +#include "hybbx/plugin.h" +#include "hybbx/auth.h" +#include "hybbx/storage.h" +#include "hybbx/texts.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Default global service name (`[service] name` in INI). */ +#define HYBBX_DEFAULT_SERVICE_NAME "hyBBX" + +typedef struct hybbx_service { + const char *name; + void *userdata; +} hybbx_service_t; + +/** Create the main HyBBX service instance. */ +hybbx_service_t *hybbx_service_create(const char *name); + +/** Destroy the service and shut down all active transports. */ +void hybbx_service_destroy(hybbx_service_t *service); + +/** + * Apply settings from an INI configuration (service name, enabled transports). + */ +hybbx_result_t hybbx_service_apply_config(hybbx_service_t *service, + const hybbx_config_t *config, + const char *config_path); + +/** + * Load a transport plugin by name (e.g. "telnet", "packet_radio"). + * Built-in plugins are linked statically; dynamic loading comes later. + */ +hybbx_result_t hybbx_service_load_transport(hybbx_service_t *service, + const char *plugin_name); + +/** Start a loaded transport with transport-specific configuration. */ +hybbx_result_t hybbx_service_start_transport(hybbx_service_t *service, + const char *plugin_name, + const char *config); + +/** Stop a running transport. */ +hybbx_result_t hybbx_service_stop_transport(hybbx_service_t *service, + const char *plugin_name); + +/** Run the main event loop until hybbx_service_stop() is called. */ +hybbx_result_t hybbx_service_run(hybbx_service_t *service); + +/** Request the main loop to exit. */ +void hybbx_service_stop(hybbx_service_t *service); + +typedef enum hybbx_shutdown_mode { + HYBBX_SHUTDOWN_NONE = 0, + HYBBX_SHUTDOWN_STOP = 1, + HYBBX_SHUTDOWN_RESTART = 2 +} hybbx_shutdown_mode_t; + +/** Store the daemon binary path used for /restart (typically argv[0]). */ +void hybbx_service_set_launch_binary(hybbx_service_t *service, const char *argv0); + +/** Sysop /shutdown or /restart — stops the main loop; restart re-execs after exit. */ +void hybbx_service_request_shutdown(hybbx_service_t *service, int restart); + +/** Shutdown mode requested while the service is still running. */ +hybbx_shutdown_mode_t hybbx_service_shutdown_mode(const hybbx_service_t *service); + +/** + * Re-exec the daemon when @ref hybbx_service_shutdown_mode is + * @ref HYBBX_SHUTDOWN_RESTART. Call before @ref hybbx_service_destroy. + * Does not return on success. + */ +void hybbx_service_restart_exec(const hybbx_service_t *service); + +const char *hybbx_service_config_path(const hybbx_service_t *service); + +hybbx_storage_t *hybbx_service_get_storage(hybbx_service_t *service); +const hybbx_auth_config_t *hybbx_service_get_auth(hybbx_service_t *service); +const hybbx_texts_config_t *hybbx_service_get_texts(hybbx_service_t *service); + +/** + * Global input prompt shown to every connected user. + * Empty string when unset (default): no visible prompt before input. + */ +const char *hybbx_service_get_prompt(const hybbx_service_t *service); + +/** Global service name from configuration (default @ref HYBBX_DEFAULT_SERVICE_NAME). */ +const char *hybbx_service_get_name(const hybbx_service_t *service); + +/** Configured maximum simultaneous online sessions (default @ref HYBBX_DEFAULT_MAX_ONLINE). */ +unsigned hybbx_service_max_online(const hybbx_service_t *service); + +/** + * Non-zero when `[service] login_announce=yes`: + * broadcast "*** User login: user@plugin" and list /who as user@plugin. + */ +int hybbx_service_login_announce(const hybbx_service_t *service); + +/** Currently connected sessions (all transports). */ +unsigned hybbx_service_active_nodes(const hybbx_service_t *service); + +/** Guest auto-disconnect timeout in seconds (from INI minutes setting). */ +unsigned hybbx_service_guest_timeout_seconds(const hybbx_service_t *service); + +struct hybbx_user_record; + +/** + * Assign the lowest free ephemeral guest slot (Guest1 … Guest25). + * Guests are not written to user files. + */ +hybbx_result_t hybbx_service_guest_assign(hybbx_service_t *service, + const char *guest_prefix, + struct hybbx_user_record *out, + unsigned *slot_out); + +/** Release a guest slot when the session ends or leaves guest mode. */ +void hybbx_service_guest_release(hybbx_service_t *service, unsigned slot); + +/** + * Reserve one node slot for a new connection. + * @return HYBBX_ERR_BUSY when @ref hybbx_service_max_online is reached. + */ +hybbx_result_t hybbx_service_acquire_node(hybbx_service_t *service); + +/** Release a node slot (pair with @ref hybbx_service_acquire_node). */ +void hybbx_service_release_node(hybbx_service_t *service); + +struct hybbx_session; + +/** Track an active connection for chat broadcast and similar features. */ +hybbx_result_t hybbx_service_attach_session(hybbx_service_t *service, + struct hybbx_session *session); + +/** Remove a session from the active connection list. */ +void hybbx_service_detach_session(hybbx_service_t *service, + struct hybbx_session *session); + +typedef void (*hybbx_service_session_visit_fn)(struct hybbx_session *session, + void *userdata); + +/** Invoke @p fn for each attached session (snapshot; lock not held in @p fn). */ +void hybbx_service_visit_sessions(hybbx_service_t *service, + hybbx_service_session_visit_fn fn, + void *userdata); + +/** + * Return a logged-in registered session for @p user_id on another connection, + * or NULL when the account is not online elsewhere. + */ +struct hybbx_session *hybbx_service_find_registered_session( + hybbx_service_t *service, + uint64_t user_id, + struct hybbx_session *exclude); + +struct hybbx_circuit_hub; +struct hybbx_circuit_hub *hybbx_service_circuit_hub(hybbx_service_t *service); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_SERVICE_H */ diff --git a/include/hybbx/session.h b/include/hybbx/session.h new file mode 100644 index 0000000..79f5343 --- /dev/null +++ b/include/hybbx/session.h @@ -0,0 +1,226 @@ +#ifndef HYBBX_SESSION_H +#define HYBBX_SESSION_H + +#include "hybbx/plugin.h" +#include "hybbx/storage.h" +#include "hybbx/mail.h" +#include "hybbx/proxymail.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct hybbx_service; + +typedef enum hybbx_session_area { + HYBBX_AREA_MAIN = 1, + HYBBX_AREA_MAIL = 2, + HYBBX_AREA_CHAT = 3, + HYBBX_AREA_CONFERENCE = 4, + /** Inter-Main mail sub-area under mail (`/proxymail`, `/mail proxymail`). */ + HYBBX_AREA_PROXYMAIL = 5, + /** Inter-Main chat sub-area under chat (`/proxychat`, `/chat proxychat`). */ + HYBBX_AREA_PROXYCHAT = 6 +} hybbx_session_area_t; + +/** Maximum nested area depth (main + sub-areas). */ +#define HYBBX_AREA_STACK_MAX 8u + +/** + * Open a new connection session. + * When auto-login is enabled (default), assigns the next free Guest1 … Guest25 + * slot in memory (not written to user files). + * When auto-login is disabled, the session stays at a login prompt for + * registered `/login` and `/register` only (no guest slots). + */ +hybbx_result_t hybbx_session_open(struct hybbx_service *service, + const hybbx_transport_plugin_t *transport, + void *transport_data, + hybbx_session_t **out); + +void hybbx_session_close(hybbx_session_t *session); + +hybbx_result_t hybbx_session_handle_input(hybbx_session_t *session, + const uint8_t *data, size_t len); + +const char *hybbx_session_username(const hybbx_session_t *session); +/** User-facing name (nickname) for the logged-in account. */ +const char *hybbx_session_display_name(const hybbx_session_t *session); +/** + * Format `username@plugin` (transport name) into @p out. + * Uses display name and session transport / record. + */ +hybbx_result_t hybbx_session_format_user_at_plugin(const hybbx_session_t *session, + char *out, size_t out_len); +uint64_t hybbx_session_id(const hybbx_session_t *session); +const hybbx_session_record_t *hybbx_session_record(const hybbx_session_t *session); +/** Store the client peer address on the session record (for security.log). */ +hybbx_result_t hybbx_session_set_remote(hybbx_session_t *session, + const char *remote); +hybbx_user_level_t hybbx_session_user_level(const hybbx_session_t *session); +int hybbx_session_is_guest(const hybbx_session_t *session); +/** Non-zero for telnet, SSH, or WebSocket — not plugin/link transports. */ +int hybbx_session_is_interactive_user(const hybbx_session_t *session); + +/** + * Non-zero when this Sysop is hidden from /who and /online: + * [monitor] invisible-sysop=yes, or /monitor on. + * Non-Sysop grant users are never hidden. + */ +int hybbx_session_hidden_from_who(const hybbx_session_t *session); + +int hybbx_session_monitor_active(const hybbx_session_t *session); +void hybbx_session_set_monitor_active(hybbx_session_t *session, int on); +/** Non-zero when the session has completed login (guest or registered). */ +int hybbx_session_logged_in(const hybbx_session_t *session); +/** Non-zero when auto_login is off: banner + prompt, registered login only. */ +int hybbx_session_login_prompt(const hybbx_session_t *session); + +/** Write raw text to the connected client (no line ending, no prompt). */ +hybbx_result_t hybbx_session_write(hybbx_session_t *session, const char *text); + +/** Write a line and append CRLF. */ +hybbx_result_t hybbx_session_write_line(hybbx_session_t *session, + const char *text); + +/** One blank line before `/command` output (not chat/mail compose). */ +hybbx_result_t hybbx_session_command_gap(hybbx_session_t *session); + +/** + * Show the global input prompt when configured. + * Default (empty prompt): no output — users type on a blank line. + */ +hybbx_result_t hybbx_session_show_prompt(hybbx_session_t *session); + +/** Clear screen and discard the current input line; show prompt when set. */ +hybbx_result_t hybbx_session_clear_terminal(hybbx_session_t *session); + +/** Non-zero when typed characters are echoed back to the client. */ +int hybbx_session_input_echo(const hybbx_session_t *session); + +/** Enable or disable per-session input echo. */ +hybbx_result_t hybbx_session_set_input_echo(hybbx_session_t *session, int enabled); + +/** Current session area (main, mail, chat, …). */ +hybbx_session_area_t hybbx_session_area(const hybbx_session_t *session); + +/** Area name: main, mail, chat. */ +const char *hybbx_session_area_name(hybbx_session_area_t area); + +/** Parse area name (main, mail, chat). */ +hybbx_session_area_t hybbx_session_area_parse(const char *name); + +/** Go up one area level (alias: /back). Clears state for the area being left. */ +hybbx_result_t hybbx_session_leave_area(hybbx_session_t *session); + +/** Return to main from any depth (alias: /menu). Clears all sub-area state. */ +hybbx_result_t hybbx_session_go_main(hybbx_session_t *session); + +/** Enter an area (pushes onto the area stack). */ +hybbx_result_t hybbx_session_enter_area(hybbx_session_t *session, + hybbx_session_area_t area); + +/** Switch the session to a registered (non-guest) account after login. */ +hybbx_result_t hybbx_session_switch_user(hybbx_session_t *session, + const hybbx_user_record_t *user); + +/** + * Periodic session maintenance (guest time limit, etc.). + * @return HYBBX_SESSION_END when the session should be closed. + */ +hybbx_result_t hybbx_session_tick(hybbx_session_t *session); + +/** Join a chat channel (enters chat area). @p channel_index is 1-based. */ +hybbx_result_t hybbx_session_join_chat_channel(hybbx_session_t *session, + unsigned channel_index); + +/** Current chat channel (1-based), or 0 when not in chat. */ +unsigned hybbx_session_chat_channel(const hybbx_session_t *session); + +struct hybbx_service *hybbx_session_service(hybbx_session_t *session); + +/** Join a private conference (conference area). Internal — use hybbx_conference_start. */ +hybbx_result_t hybbx_session_join_conference(hybbx_session_t *session, + const char *topic, + const char *partner_username); + +void hybbx_session_clear_conference(hybbx_session_t *session); + +int hybbx_session_conference_may_invite(hybbx_session_t *session, + const char *target); +void hybbx_session_conference_invite_sent(hybbx_session_t *session, + const char *target); +void hybbx_session_set_conference_invite(hybbx_session_t *session, + const char *from_username, + const char *topic); +/** Optional deadline (0 = none). Used by monitor-initiated invites. */ +void hybbx_session_set_conference_invite_deadline(hybbx_session_t *session, + time_t deadline); +time_t hybbx_session_conference_invite_deadline(const hybbx_session_t *session); +void hybbx_session_clear_conference_invite(hybbx_session_t *session); +const char *hybbx_session_conference_invite_from(const hybbx_session_t *session); +const char *hybbx_session_conference_invite_topic(const hybbx_session_t *session); +const char *hybbx_session_conference_partner(const hybbx_session_t *session); + +/** Enter local mail area (does not enter proxymail). */ +hybbx_result_t hybbx_session_enter_mail(hybbx_session_t *session); + +/** Enter proxymail sub-area (pushes mail when needed). */ +hybbx_result_t hybbx_session_enter_proxymail(hybbx_session_t *session); + +/** Enter local chat area without joining a channel. */ +hybbx_result_t hybbx_session_enter_chat(hybbx_session_t *session); + +/** Enter proxychat sub-area (pushes chat when needed). */ +hybbx_result_t hybbx_session_enter_proxychat(hybbx_session_t *session); + +/** Non-zero when composing an outbound mail message. */ +int hybbx_session_mail_composing(const hybbx_session_t *session); + +/** Start mail compose to @p to_user with @p subject; enters mail area. */ +hybbx_result_t hybbx_session_mail_compose_start(hybbx_session_t *session, + const char *to_user, + const char *subject); + +/** Cancel an in-progress mail compose. */ +void hybbx_session_mail_compose_cancel(hybbx_session_t *session); + +/** Body accumulated so far during compose (NUL-terminated). */ +const char *hybbx_session_mail_compose_body(const hybbx_session_t *session); + +const char *hybbx_session_mail_compose_to(const hybbx_session_t *session); +const char *hybbx_session_mail_compose_subject(const hybbx_session_t *session); + +/** Non-zero when composing proxymail (`user@remote-main` recipient). */ +int hybbx_session_proxymail_composing(const hybbx_session_t *session); + +hybbx_result_t hybbx_session_proxymail_compose_start(hybbx_session_t *session, + const char *to_address, + const char *subject); + +void hybbx_session_proxymail_compose_cancel(hybbx_session_t *session); + +const char *hybbx_session_proxymail_compose_body(const hybbx_session_t *session); +const char *hybbx_session_proxymail_compose_to(const hybbx_session_t *session); +const char *hybbx_session_proxymail_compose_subject( + const hybbx_session_t *session); + +/** Wall-clock time when this session connected (for bandwidth policy ordering). */ +time_t hybbx_session_connected_at(const hybbx_session_t *session); + +/** Non-zero when the session is frozen by circuit bandwidth limits. */ +int hybbx_session_bandwidth_paused(const hybbx_session_t *session); + +void hybbx_session_set_bandwidth_paused(hybbx_session_t *session, int paused); + +/** + * End the session after sending @ref HYBBX_BANDWIDTH_DISCONNECT_MSG. + * Newest-first under low-bandwidth link pressure. + */ +void hybbx_session_disconnect_bandwidth(hybbx_session_t *session); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_SESSION_H */ diff --git a/include/hybbx/socket.h b/include/hybbx/socket.h new file mode 100644 index 0000000..1baa639 --- /dev/null +++ b/include/hybbx/socket.h @@ -0,0 +1,35 @@ +#ifndef HYBBX_SOCKET_H +#define HYBBX_SOCKET_H + +#if defined(__linux__) || defined(__GLIBC__) +#define _DEFAULT_SOURCE 1 +#endif + +#include <sys/socket.h> + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** Suppress SIGPIPE on send() (SO_NOSIGPIPE on BSD/MacOS; noop on Linux). */ +void hybbx_socket_nosigpipe(int fd); + +/** + * Format the peer IP address of a connected socket into @p buf. + * IPv4 dotted-quad or IPv6 colon form (no brackets). + */ +hybbx_result_t hybbx_socket_peer_name(int fd, char *buf, size_t buf_len); + +/** Log bind(2) failure; hints when @c EADDRINUSE (HyBBX already running). */ +void hybbx_socket_log_bind_failure(const char *component, const char *addr, + unsigned port); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_SOCKET_H */ diff --git a/include/hybbx/ssh.h b/include/hybbx/ssh.h new file mode 100644 index 0000000..2555198 --- /dev/null +++ b/include/hybbx/ssh.h @@ -0,0 +1,57 @@ +#ifndef HYBBX_SSH_H +#define HYBBX_SSH_H + +#include "hybbx/limits.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Default HyBBX SSH TCP port (non-privileged; parallels telnet 2323). */ +#define HYBBX_SSH_DEFAULT_PORT 3232u + +#define HYBBX_SSH_BIND_V4_MAX 64 +#define HYBBX_SSH_BIND_V6_MAX 64 +#define HYBBX_SSH_HOSTKEY_DIR_MAX HYBBX_PATH_MAX + +#define HYBBX_SSH_DEFAULT_BIND_V4 "0.0.0.0" +#define HYBBX_SSH_DEFAULT_BIND_V6 "::" +#define HYBBX_SSH_DEFAULT_HOSTKEY_DIR "keys" + +#define HYBBX_SSH_HOSTKEY_ED25519 "ssh_host_ed25519_key" + +/** Regenerate Ed25519 host keys when older than this (days). */ +#define HYBBX_SSH_HOSTKEY_VALID_DAYS 1825u + +typedef struct hybbx_ssh_config { + char bind_v4[HYBBX_SSH_BIND_V4_MAX]; + char bind_v6[HYBBX_SSH_BIND_V6_MAX]; + char hostkey_dir[HYBBX_SSH_HOSTKEY_DIR_MAX]; + unsigned int port; + int ipv4; + int ipv6; +} hybbx_ssh_config_t; + +void hybbx_ssh_config_defaults(hybbx_ssh_config_t *config); + +/** + * Parse a semicolon-separated key=value transport config string + * (from transport.ssh INI section). + */ +hybbx_result_t hybbx_ssh_config_parse(const char *config, + hybbx_ssh_config_t *out); + +/** + * Ensure Ed25519 host keys exist under @p keys_dir (resolved path). + * Writes the private key path into @p hostkey_path. + */ +hybbx_result_t hybbx_ssh_keys_ensure(const char *keys_dir, + char *hostkey_path, + size_t hostkey_path_len); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_SSH_H */ diff --git a/include/hybbx/storage.h b/include/hybbx/storage.h new file mode 100644 index 0000000..f4b4802 --- /dev/null +++ b/include/hybbx/storage.h @@ -0,0 +1,178 @@ +#ifndef HYBBX_STORAGE_H +#define HYBBX_STORAGE_H + +#include "hybbx/auth.h" +#include "hybbx/limits.h" +#include "hybbx/types.h" + +#include <stddef.h> +#include <stdint.h> +#include <time.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum hybbx_storage_backend_kind { + HYBBX_STORAGE_FLATFILE = 1, + HYBBX_STORAGE_SQLITE = 2, + HYBBX_STORAGE_MYSQL = 3, + HYBBX_STORAGE_MARIADB = 4 +} hybbx_storage_backend_kind_t; + +#define HYBBX_USER_NAME_MAX 64 +#define HYBBX_USER_NICKNAME_MAX 64 +#define HYBBX_USER_FULL_NAME_MAX 128 +#define HYBBX_USER_COUNTRY_MAX 64 +#define HYBBX_USER_LOCATION_MAX 128 +#define HYBBX_USER_EMAIL_MAX 128 +#define HYBBX_USER_PASSWORD_MAX HYBBX_PASSWORD_STORED_MAX +#define HYBBX_TRANSPORT_NAME_MAX 32 +/** Peer address string (IPv4 or bracketed IPv6). */ +#define HYBBX_REMOTE_ADDR_MAX 64 + +/** Maximum registered user records per INI user-file shard. */ +#define HYBBX_USERS_PER_FILE 50u + +/** INI section prefix for one user: [user.<id>]. */ +#define HYBBX_USER_INI_SECTION_PREFIX "user." + +typedef struct hybbx_user_registration { + char username[HYBBX_USER_NAME_MAX]; + char nickname[HYBBX_USER_NICKNAME_MAX]; + char full_name[HYBBX_USER_FULL_NAME_MAX]; + char country[HYBBX_USER_COUNTRY_MAX]; + char location[HYBBX_USER_LOCATION_MAX]; + char email[HYBBX_USER_EMAIL_MAX]; + char password[HYBBX_USER_PASSWORD_MAX]; +} hybbx_user_registration_t; + +/** Validate profile fields only (full name, country, location, email). */ +int hybbx_user_profile_valid(const hybbx_user_registration_t *reg); + +/** Validate registration profile fields (username + full name, country, location, email). */ +int hybbx_registration_valid(const hybbx_user_registration_t *reg, + const char *guest_prefix); + +typedef struct hybbx_user_record { + uint64_t id; + char username[HYBBX_USER_NAME_MAX]; + char nickname[HYBBX_USER_NICKNAME_MAX]; + hybbx_user_level_t level; + int active; + time_t created_at; + char full_name[HYBBX_USER_FULL_NAME_MAX]; + char country[HYBBX_USER_COUNTRY_MAX]; + char location[HYBBX_USER_LOCATION_MAX]; + char email[HYBBX_USER_EMAIL_MAX]; + char password[HYBBX_USER_PASSWORD_MAX]; + time_t last_login_at; +} hybbx_user_record_t; + +typedef struct hybbx_session_record { + uint64_t session_id; + uint64_t user_id; + char username[HYBBX_USER_NAME_MAX]; + char transport[HYBBX_TRANSPORT_NAME_MAX]; + char remote[HYBBX_REMOTE_ADDR_MAX]; + time_t connected_at; + time_t disconnected_at; + int active; +} hybbx_session_record_t; + +typedef struct hybbx_storage hybbx_storage_t; + +/** Paths and backup settings for SQLite (`[storage]`). */ +typedef struct hybbx_storage_sql_config { + char user_db[HYBBX_PATH_MAX]; + char mail_db[HYBBX_PATH_MAX]; + unsigned backup_interval_sec; + char backup_path[HYBBX_PATH_MAX]; +} hybbx_storage_sql_config_t; + +typedef struct hybbx_storage_options { + hybbx_storage_backend_kind_t backend; + const char *path; + const char *guest_prefix; + const hybbx_storage_sql_config_t *sql_cfg; +} hybbx_storage_options_t; + +struct hybbx_config; + +void hybbx_storage_sql_config_defaults(hybbx_storage_sql_config_t *cfg); + +/** Resolve `user_db` / `mail_db` under @p storage_path from INI `[storage]`. */ +void hybbx_storage_sql_config_apply(hybbx_storage_sql_config_t *cfg, + const struct hybbx_config *config, + const char *storage_path); + +const hybbx_storage_sql_config_t *hybbx_storage_sql_config( + const hybbx_storage_t *storage); + +/** Copy SQLite DB files to fallback paths (no-op when not SQLite). */ +void hybbx_storage_backup_tick(hybbx_storage_t *storage); + +hybbx_storage_t *hybbx_storage_open(const hybbx_storage_options_t *options); +void hybbx_storage_close(hybbx_storage_t *storage); + +hybbx_storage_backend_kind_t hybbx_storage_backend(const hybbx_storage_t *storage); + +/** Root data directory path, or NULL when unset. */ +const char *hybbx_storage_root_path(const hybbx_storage_t *storage); + +/** + * Register a new user account (level user, inactive until Sysop/Admin activates). + */ +hybbx_result_t hybbx_storage_register_user(hybbx_storage_t *storage, + const hybbx_user_registration_t *reg, + hybbx_user_record_t *out); + +/** Look up a user by login name (case-insensitive; stored lowercase). */ +hybbx_result_t hybbx_storage_find_user(hybbx_storage_t *storage, + const char *username, + hybbx_user_record_t *out); + +/** Resolve by login name or nickname (case-insensitive). */ +hybbx_result_t hybbx_storage_resolve_user(hybbx_storage_t *storage, + const char *name, + hybbx_user_record_t *out); + +/** Count existing accounts at a given level (e.g. enforce one Sysop). */ +hybbx_result_t hybbx_storage_count_level(hybbx_storage_t *storage, + hybbx_user_level_t level, + size_t *count); + +/** + * Iterate all user records. @p fn returns HYBBX_OK to continue; any other + * result stops iteration and is returned from this function. + */ +typedef hybbx_result_t (*hybbx_storage_user_fn)(const hybbx_user_record_t *user, + void *ctx); + +hybbx_result_t hybbx_storage_foreach_user(hybbx_storage_t *storage, + hybbx_storage_user_fn fn, + void *ctx); + +hybbx_result_t hybbx_storage_session_begin(hybbx_storage_t *storage, + const hybbx_user_record_t *user, + const char *transport, + hybbx_session_record_t *out); + +hybbx_result_t hybbx_storage_session_end(hybbx_storage_t *storage, + uint64_t session_id); + +/** + * Replace an existing user record (matched by @p user->id). + */ +hybbx_result_t hybbx_storage_update_user(hybbx_storage_t *storage, + const hybbx_user_record_t *user); + +/** Remove a user record from storage (matched by @p user_id). */ +hybbx_result_t hybbx_storage_delete_user(hybbx_storage_t *storage, + uint64_t user_id); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_STORAGE_H */ diff --git a/include/hybbx/telnet.h b/include/hybbx/telnet.h new file mode 100644 index 0000000..956f1f5 --- /dev/null +++ b/include/hybbx/telnet.h @@ -0,0 +1,40 @@ +#ifndef HYBBX_TELNET_H +#define HYBBX_TELNET_H + +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Default HyBBX telnet TCP port (BBX / terminal convention). */ +#define HYBBX_TELNET_DEFAULT_PORT 2323u + +#define HYBBX_TELNET_BIND_V4_MAX 64 +#define HYBBX_TELNET_BIND_V6_MAX 64 + +#define HYBBX_TELNET_DEFAULT_BIND_V4 "0.0.0.0" +#define HYBBX_TELNET_DEFAULT_BIND_V6 "::" + +typedef struct hybbx_telnet_config { + char bind_v4[HYBBX_TELNET_BIND_V4_MAX]; + char bind_v6[HYBBX_TELNET_BIND_V6_MAX]; + unsigned int port; + int ipv4; + int ipv6; +} hybbx_telnet_config_t; + +void hybbx_telnet_config_defaults(hybbx_telnet_config_t *config); + +/** + * Parse a semicolon-separated key=value transport config string + * (from transport.telnet INI section). + */ +hybbx_result_t hybbx_telnet_config_parse(const char *config, + hybbx_telnet_config_t *out); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_TELNET_H */ diff --git a/include/hybbx/terminal.h b/include/hybbx/terminal.h new file mode 100644 index 0000000..fec0871 --- /dev/null +++ b/include/hybbx/terminal.h @@ -0,0 +1,39 @@ +#ifndef HYBBX_TERMINAL_H +#define HYBBX_TERMINAL_H + +#include "hybbx/types.h" + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct hybbx_session; + +/** + * HyBBX uses a single terminal palette: light gray text on a black background. + * ANSI SGR: foreground 37, background 40. + */ +#define HYBBX_TERM_SGR_LIGHTGRAY_ON_BLACK "\033[37;40m" + +/** Clear screen and move cursor home (requires `traffic.ansi = yes`). */ +#define HYBBX_TERM_CLEAR_SCREEN "\033[2J\033[H" + +/** Apply the HyBBX terminal colors on a session (sent once at connect). */ +hybbx_result_t hybbx_term_init_session(struct hybbx_session *session); + +/** Clear the client screen (ANSI or plain newlines). */ +hybbx_result_t hybbx_term_clear_screen(struct hybbx_session *session); + +/** + * Copy @p src to @p dst without ANSI escape sequences (colors, cursor, etc.). + * @return length of written string excluding the terminator. + */ +size_t hybbx_term_copy_plain(const char *src, char *dst, size_t dst_size); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_TERMINAL_H */ diff --git a/include/hybbx/texts.h b/include/hybbx/texts.h new file mode 100644 index 0000000..ef1b9e3 --- /dev/null +++ b/include/hybbx/texts.h @@ -0,0 +1,84 @@ +#ifndef HYBBX_TEXTS_H +#define HYBBX_TEXTS_H + +#include "hybbx/types.h" +#include "hybbx/limits.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct hybbx_session; + +#define HYBBX_DEFAULT_TEXTS_PATH HYBBX_DIR_TEXT + +#define HYBBX_TEXT_BANNER "banner.txt" +#define HYBBX_TEXT_MOTD "motd.txt" +#define HYBBX_TEXT_NEWS "news.txt" +#define HYBBX_TEXT_RULES "rules.txt" +#define HYBBX_TEXT_VERSION "version.txt" + +/** Substituted in banner.txt / version.txt — HyBBX version number (e.g. 2.4.0). */ +#define HYBBX_BANNER_TOKEN_VERSION "@version@" +/** Substituted in banner.txt line 2. */ +#define HYBBX_BANNER_TOKEN_SERVICE "@service@" +/** Substituted in motd.txt (current session nickname). */ +#define HYBBX_TEXT_TOKEN_USERNAME "@username@" +/** Substituted in version.txt — host operating system name (e.g. Linux). */ +#define HYBBX_TEXT_TOKEN_OS "@os@" +/** Substituted in text files — local time per `[time]` (default HH:MM:SS). */ +#define HYBBX_TEXT_TOKEN_TIME "%time%" +/** Substituted in text files — local date per `[time] date=` (default YYYY/MM/DD). */ +#define HYBBX_TEXT_TOKEN_DATE "%date%" + +#define HYBBX_TEXTS_PATH_MAX 512 + +typedef struct hybbx_texts_config { + char path[HYBBX_TEXTS_PATH_MAX]; +} hybbx_texts_config_t; + +void hybbx_texts_config_defaults(hybbx_texts_config_t *texts); + +/** + * Build full path to a text file under the configured texts directory. + * Returns HYBBX_OK on success. + */ +hybbx_result_t hybbx_texts_resolve(const hybbx_texts_config_t *texts, + const char *filename, + char *out, size_t out_len); + +/** + * Send a text file line-by-line to the session (connection output). + * Expands @version@, @service@, @username@, @os@, %time%, and %date%. + * Missing files are skipped silently. + */ +hybbx_result_t hybbx_texts_send_file(const hybbx_texts_config_t *texts, + struct hybbx_session *session, + const char *filename); + +/** + * Send banner.txt with @version@ and @service@ tokens expanded. + */ +hybbx_result_t hybbx_texts_send_banner(const hybbx_texts_config_t *texts, + struct hybbx_session *session, + const char *version, + const char *service_name); + +/** + * Send motd.txt with @username@ expanded from @p session. + */ +hybbx_result_t hybbx_texts_send_motd(const hybbx_texts_config_t *texts, + struct hybbx_session *session); + +/** + * Send version.txt with @version@ and @os@ expanded from this host. + * Falls back to the built-in two-line version block if the file is missing. + */ +hybbx_result_t hybbx_texts_send_version(const hybbx_texts_config_t *texts, + struct hybbx_session *session); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_TEXTS_H */ diff --git a/include/hybbx/tnc.h b/include/hybbx/tnc.h new file mode 100644 index 0000000..9655aa8 --- /dev/null +++ b/include/hybbx/tnc.h @@ -0,0 +1,240 @@ +#ifndef HYBBX_TNC_H +#define HYBBX_TNC_H + +/** + * Unified TNC / AX.25 link driver for HyBBX packet radio. + * + * Supported hardware profiles (see docs/TNCS.md): + * - Landolt TNC2C, classic TNC-2 / PK-TNC2 (TheFirmware) + * - AEA PK-232, MFJ-1278, Kantronics KPC + * - Generic TNC2-class controllers + * + * Not in packet_radio: BayCom/based SER12 / PC-COM TCM3105 — use MAX25 + * bcpr (`max25e0`) then optional HyBBX baycom transport or [max25] attach. + * UN1TME = TNCs only. + * + * Host protocols: KISS, TNC2 host mode, 6PACK (DF6BU). + */ + +#include "hybbx/ax25.h" +#include "hybbx/packet_radio.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define HYBBX_TNC2C_DEFAULT_HOST_BAUD HYBBX_PACKET_RADIO_DEFAULT_BAUD +#define HYBBX_TNC2C_DEFAULT_RADIO_BAUD 1200u +#define HYBBX_AFSK_RADIO_BAUD 1200u +#define HYBBX_G3RUH_RADIO_BAUD 9600u +#define HYBBX_TNC2C_CLOCK_4_9_MHZ 4.9f +#define HYBBX_TNC2C_CLOCK_9_8_MHZ 9.8f +#define HYBBX_TNC2C_CLOCK_10_MHZ 10.0f +#define HYBBX_TNC2C_HOST_BAUD_MIN 300u +#define HYBBX_TNC2C_HOST_BAUD_MAX 38400u +#define HYBBX_TNC2C_DATA_BITS 7u + +typedef enum hybbx_tnc_serial_parity { + HYBBX_TNC_SERIAL_PARITY_UNSET = 0, + HYBBX_TNC_SERIAL_PARITY_NONE = 1, + HYBBX_TNC_SERIAL_PARITY_EVEN = 2, + HYBBX_TNC_SERIAL_PARITY_ODD = 3 +} hybbx_tnc_serial_parity_t; + +#define HYBBX_PACKET_RADIO_VIA_MAX 8 + +typedef enum hybbx_tnc2c_modem { + HYBBX_TNC2C_MODEM_TCM3105 = 1, + HYBBX_TNC2C_MODEM_9600 = 2, /**< G3RUH-compatible 9600 baud FSK */ + HYBBX_TNC2C_MODEM_19200 = 3 +} hybbx_tnc2c_modem_t; + +/** + * Over-the-air modulation standard. + * AFSK (Bell 202 / TCM3105 @ 1200) is the classic CB packet default. + * G3RUH FSK @ 9600 baud for higher throughput (requires compatible modem/TNC). + */ +typedef enum hybbx_packet_radio_modulation { + HYBBX_PACKET_RADIO_MOD_UNSET = 0, + HYBBX_PACKET_RADIO_MOD_AFSK = 1, + HYBBX_PACKET_RADIO_MOD_G3RUH_FSK = 2 +} hybbx_packet_radio_modulation_t; + +#define HYBBX_PACKET_RADIO_MOD_DEFAULT HYBBX_PACKET_RADIO_MOD_AFSK + +typedef enum hybbx_packet_radio_protocol { + HYBBX_PACKET_RADIO_PROTO_KISS = 1, + HYBBX_PACKET_RADIO_PROTO_HOSTMODE = 2, + HYBBX_PACKET_RADIO_PROTO_SIXPACK = 3 +} hybbx_packet_radio_protocol_t; + +typedef enum hybbx_packet_radio_tnc { + HYBBX_PACKET_RADIO_TNC_TNC2C = 1, + HYBBX_PACKET_RADIO_TNC_BAYCOM = 2, + HYBBX_PACKET_RADIO_TNC_PCCOM = 3, + HYBBX_PACKET_RADIO_TNC_GENERIC = 4, + HYBBX_PACKET_RADIO_TNC_TNC2 = 5, + HYBBX_PACKET_RADIO_TNC_PK232 = 6, + HYBBX_PACKET_RADIO_TNC_MFJ1278 = 7, + HYBBX_PACKET_RADIO_TNC_KANTRONICS = 8 +} hybbx_packet_radio_tnc_t; + +/** How HyBBX enters KISS mode on the host serial link. */ +typedef enum hybbx_kiss_entry { + HYBBX_KISS_ENTRY_UNSET = 0, + HYBBX_KISS_ENTRY_NONE = 1, + HYBBX_KISS_ENTRY_KISS_ON = 2, + HYBBX_KISS_ENTRY_ESC_AT_K = 3, + HYBBX_KISS_ENTRY_AUTO = 4 +} hybbx_kiss_entry_t; + +/** How HyBBX leaves KISS mode on shutdown. */ +typedef enum hybbx_kiss_exit { + HYBBX_KISS_EXIT_UNSET = 0, + HYBBX_KISS_EXIT_NONE = 1, + HYBBX_KISS_EXIT_KISS_OFF = 2, + HYBBX_KISS_EXIT_KISS_FRAME = 3, + HYBBX_KISS_EXIT_AUTO = 4 +} hybbx_kiss_exit_t; + +/** + * RF service band — drives safe duplex defaults. + * CB is half-duplex only (shared channel). Amateur may use full-duplex. + */ +typedef enum hybbx_packet_radio_band { + HYBBX_PACKET_RADIO_BAND_UNSET = 0, + HYBBX_PACKET_RADIO_BAND_CB = 1, + HYBBX_PACKET_RADIO_BAND_AMATEUR = 2 +} hybbx_packet_radio_band_t; + +/** + * Radio link duplex mode (distinct from host serial full-duplex). + * half = one direction at a time on the RF channel (CB / classic packet). + * full = TNC may key TX while receiving (amateur split / full-dup setups). + */ +typedef enum hybbx_packet_radio_duplex { + HYBBX_PACKET_RADIO_DUPLEX_UNSET = 0, + HYBBX_PACKET_RADIO_DUPLEX_HALF = 1, + HYBBX_PACKET_RADIO_DUPLEX_FULL = 2 +} hybbx_packet_radio_duplex_t; + +#define HYBBX_PACKET_RADIO_DUPLEX_DEFAULT HYBBX_PACKET_RADIO_DUPLEX_HALF + +typedef struct hybbx_tnc_params { + float clock_mhz; + hybbx_tnc2c_modem_t modem; + hybbx_packet_radio_modulation_t modulation; + unsigned int radio_baud; + unsigned int kiss_port; + unsigned int txdelay; + unsigned int persist; + unsigned int slot; + unsigned int txtail; + hybbx_packet_radio_band_t band; + hybbx_packet_radio_duplex_t duplex; + int fullduplex; + hybbx_kiss_entry_t kiss_entry; + hybbx_kiss_exit_t kiss_exit; + int host_connect_on_start; + unsigned int data_bits; + hybbx_tnc_serial_parity_t serial_parity; + unsigned int stop_bits; + int assert_modem_lines; +} hybbx_tnc_params_t; + +typedef struct hybbx_packet_radio_config { + hybbx_packet_radio_device_type_t device_type; + char *device; + unsigned int baud; + hybbx_packet_radio_tnc_t tnc; + hybbx_packet_radio_protocol_t protocol; + hybbx_tnc_params_t params; + char *mycall; + char *dest_call; + char *via[HYBBX_PACKET_RADIO_VIA_MAX]; + unsigned via_count; + char *circuit_host; + unsigned circuit_port; + char *link_id; + char *link_password; + char *link_role; + char *frequency_mhz; +} hybbx_packet_radio_config_t; + +typedef struct hybbx_tnc hybbx_tnc_t; + +typedef void (*hybbx_tnc_ui_cb)(const uint8_t *payload, size_t len, + const hybbx_ax25_path_t *path, + void *userdata); + +/** Raw AX.25 frame from KISS/6PACK (before UI parsing). */ +typedef void (*hybbx_tnc_frame_cb)(const uint8_t *frame, size_t len, + void *userdata); + +hybbx_result_t hybbx_tnc_open(hybbx_tnc_t **out, + const hybbx_packet_radio_config_t *config, + hybbx_tnc_frame_cb frame_cb, + hybbx_tnc_ui_cb ui_cb, + void *rx_userdata); + +void hybbx_tnc_close(hybbx_tnc_t *tnc); + +hybbx_result_t hybbx_tnc_poll(hybbx_tnc_t *tnc); + +/** POSIX serial fd for poll/select; returns -1 when unavailable. */ +int hybbx_tnc_serial_fd(const hybbx_tnc_t *tnc); + +/** Send a raw AX.25 frame (already built) to the TNC. */ +hybbx_result_t hybbx_tnc_send_frame(hybbx_tnc_t *tnc, + const uint8_t *frame, size_t len); + +/** Build and send an AX.25 UI frame from text payload. */ +hybbx_result_t hybbx_tnc_send_ui(hybbx_tnc_t *tnc, + const uint8_t *payload, size_t len); + +/** Send bytes during host-mode connected converse. */ +hybbx_result_t hybbx_tnc_send_converse(hybbx_tnc_t *tnc, + const char *data, size_t len); + +int hybbx_tnc_host_connected(const hybbx_tnc_t *tnc); + +hybbx_packet_radio_duplex_t hybbx_tnc_duplex(const hybbx_tnc_t *tnc); +hybbx_packet_radio_band_t hybbx_tnc_band(const hybbx_tnc_t *tnc); +int hybbx_tnc_duplex_is_full(const hybbx_tnc_t *tnc); + +const char *hybbx_packet_radio_duplex_name(hybbx_packet_radio_duplex_t duplex); +const char *hybbx_packet_radio_band_name(hybbx_packet_radio_band_t band); + +const char *hybbx_packet_radio_modulation_name( + hybbx_packet_radio_modulation_t modulation); + +int hybbx_packet_radio_modulation_is_g3ruh( + hybbx_packet_radio_modulation_t modulation); + +/** Apply g3ruh_fsk / modulation keys to modem, radio_baud, and timing. */ +hybbx_result_t hybbx_tnc_finalize_modulation(hybbx_packet_radio_config_t *cfg); + +/** Apply band/duplex rules and sync @p fullduplex for the TNC firmware. */ +hybbx_result_t hybbx_tnc_finalize_radio_duplex(hybbx_packet_radio_config_t *cfg); + +/** Apply profile-specific defaults (TNC2C, generic, …). */ +hybbx_result_t hybbx_tnc_profile_apply_defaults(hybbx_packet_radio_config_t *cfg); + +/** Apply host serial line defaults (TNC2C: 7E1 + RTS/DTR). */ +hybbx_result_t hybbx_tnc_finalize_serial_line(hybbx_packet_radio_config_t *cfg); + +/** Apply kiss_entry / kiss_exit defaults from TNC profile when unset. */ +hybbx_result_t hybbx_tnc_finalize_kiss_entry(hybbx_packet_radio_config_t *cfg); + +/** Apply CSMA defaults (Persist/SlotTime) from band when unset in INI. */ +hybbx_result_t hybbx_tnc_finalize_csma(hybbx_packet_radio_config_t *cfg); + +const char *hybbx_packet_radio_tnc_name(hybbx_packet_radio_tnc_t tnc); +const char *hybbx_kiss_entry_name(hybbx_kiss_entry_t entry); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_TNC_H */ diff --git a/include/hybbx/tnc2c.h b/include/hybbx/tnc2c.h new file mode 100644 index 0000000..42f5277 --- /dev/null +++ b/include/hybbx/tnc2c.h @@ -0,0 +1,27 @@ +#ifndef HYBBX_TNC2C_H +#define HYBBX_TNC2C_H + +/** + * Backward-compatible header for the Landolt TNC2C driver. + * New code should include hybbx/tnc.h. + */ + +#include "hybbx/tnc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef hybbx_tnc_t hybbx_tnc2c_t; +typedef hybbx_tnc_params_t hybbx_tnc2c_params_t; + +/* hybbx_tnc2c_open removed — use hybbx_tnc_open() with RX callback. */ +#define hybbx_tnc2c_close hybbx_tnc_close +#define hybbx_tnc2c_poll hybbx_tnc_poll +#define hybbx_tnc2c_send_frame hybbx_tnc_send_frame + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_TNC2C_H */ diff --git a/include/hybbx/traffic.h b/include/hybbx/traffic.h new file mode 100644 index 0000000..8d06cf3 --- /dev/null +++ b/include/hybbx/traffic.h @@ -0,0 +1,59 @@ +#ifndef HYBBX_TRAFFIC_H +#define HYBBX_TRAFFIC_H + +#include "hybbx/types.h" + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct hybbx_config; +struct hybbx_session; + +/** Host link speed HyBBX is optimized for (8N1, ~1 byte per 10 bit times). */ +#define HYBBX_BAUD2400 2400u + +/** Default terminal width (columns). */ +#define HYBBX_LINE_WIDTH 80u + +/** Maximum configurable `line_width` in INI. */ +#define HYBBX_LINE_WIDTH_MAX 132u + +/** Maximum user input line (commands / chat). */ +#define HYBBX_LINE_MAX 80u + +/** Typical AX.25 UI payload ceiling used for outbound chunk sizing. */ +#define HYBBX_AX25_PAYLOAD_MAX 256u + +typedef struct hybbx_traffic_config { + unsigned baud; + unsigned line_width; + int pace_output; + int ansi; + int input_echo; +} hybbx_traffic_config_t; + +void hybbx_traffic_config_defaults(hybbx_traffic_config_t *cfg); +void hybbx_traffic_config_apply(const struct hybbx_config *config); +const hybbx_traffic_config_t *hybbx_traffic_config_get(void); + +/** + * Microseconds to wait after one 8N1 byte at @p baud (0 when pacing is off). + */ +unsigned hybbx_traffic_byte_delay_us(unsigned baud); + +/** + * Emit @p data to @p session with plain-ASCII policy, column wrap, and pacing. + * @p out_col is updated (wrap position on the current output line). + */ +hybbx_result_t hybbx_traffic_emit(struct hybbx_session *session, + unsigned *out_col, + const char *data, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_TRAFFIC_H */ diff --git a/include/hybbx/types.h b/include/hybbx/types.h new file mode 100644 index 0000000..775a711 --- /dev/null +++ b/include/hybbx/types.h @@ -0,0 +1,42 @@ +#ifndef HYBBX_TYPES_H +#define HYBBX_TYPES_H + +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum hybbx_result { + HYBBX_OK = 0, + /** Input is a local/mailbox command (not a HyBBX '/' command). */ + HYBBX_LOCAL_CMD = 1, + /** Session should be closed (e.g. /exit). */ + HYBBX_SESSION_END = 2, + HYBBX_ERR_INVALID = -1, + HYBBX_ERR_NOMEM = -2, + HYBBX_ERR_NOT_FOUND = -3, + HYBBX_ERR_IO = -4, + HYBBX_ERR_UNSUPPORTED = -5, + HYBBX_ERR_BUSY = -6, + HYBBX_ERR_DENIED = -7 +} hybbx_result_t; + +typedef enum hybbx_transport_kind { + HYBBX_TRANSPORT_TELNET = 1, + HYBBX_TRANSPORT_PACKET_RADIO = 2, + HYBBX_TRANSPORT_CIRCUIT = 3, + HYBBX_TRANSPORT_ARDOP = 4, + HYBBX_TRANSPORT_CRDOP = 5, + HYBBX_TRANSPORT_SSH = 6, + HYBBX_TRANSPORT_WEBSOCKET = 7, + HYBBX_TRANSPORT_BAYCOM = 8, + HYBBX_TRANSPORT_MAINS_PROXY = 9, +} hybbx_transport_kind_t; + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_TYPES_H */ diff --git a/include/hybbx/util.h b/include/hybbx/util.h new file mode 100644 index 0000000..de439b4 --- /dev/null +++ b/include/hybbx/util.h @@ -0,0 +1,143 @@ +#ifndef HYBBX_UTIL_H +#define HYBBX_UTIL_H + +#include "hybbx/types.h" + +#include <stddef.h> +#include <time.h> + +struct tm; + +struct hybbx_config; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Safe string copy (always NUL-terminates when @p dst_size > 0). + * Returns bytes copied excluding the terminator. + */ +size_t hybbx_strlcpy(char *dst, const char *src, size_t dst_size); + +/** + * Join @p base and @p name into @p out with a single '/'. + * Rejects empty components and path traversal (".."). + */ +hybbx_result_t hybbx_path_join(char *out, size_t out_len, + const char *base, const char *name); + +/** Default relative storage path (@ref HYBBX_DIR_DATA). */ +hybbx_result_t hybbx_default_user_data_path(char *out, size_t out_len); + +/** + * Expand a config path: `~` → $HOME, `~/foo` → $HOME/foo. + * Empty or NULL @p path → @ref HYBBX_DIR_DATA. Other paths copied unchanged. + */ +hybbx_result_t hybbx_path_expand(char *out, size_t out_len, const char *path); + +/** Set the HyBBX install root used by @ref hybbx_path_resolve. */ +void hybbx_install_root_set(const char *root); + +/** Install root set by @ref hybbx_install_root_set, or NULL when unset. */ +const char *hybbx_install_root_get(void); + +/** + * Resolve a config path: @ref hybbx_path_expand, then prefix relative paths + * with the install root when set. + */ +hybbx_result_t hybbx_path_resolve(char *out, size_t out_len, const char *path); + +/** Parent directory of @p path (POSIX `/` rules). */ +hybbx_result_t hybbx_path_dirname(const char *path, char *out, size_t out_len); + +/** + * Host operating system name for display (e.g. Linux, FreeBSD, MacOS, Windows). + * Does not include OS version or kernel release. + */ +hybbx_result_t hybbx_platform_os_name(char *out, size_t out_len); + +/** Return non-zero when @p len is safe for HyBBX allocations. */ +int hybbx_size_ok(size_t len); + +/** + * HyBBX boolean configuration standard. + * Canonical written form: @c yes / @c no (see @ref HYBBX_BOOL_YES / @ref HYBBX_BOOL_NO). + * Accepted true: yes, true, enable, enabled, on, 1 + * Accepted false: no, false, disable, disabled, off, 0 + * Matching is case-insensitive. + */ +#define HYBBX_BOOL_YES "yes" +#define HYBBX_BOOL_NO "no" + +/** Return non-zero when @p value is a recognized true token. */ +int hybbx_bool_is_true(const char *value); + +/** Return non-zero when @p value is a recognized false token. */ +int hybbx_bool_is_false(const char *value); + +/** + * Parse a boolean string. Returns 1 or 0 when recognized; otherwise + * returns @p default_value (also used when @p value is NULL or empty). + */ +int hybbx_parse_bool(const char *value, int default_value); + +/** Canonical @c yes / @c no string for a boolean value. */ +const char *hybbx_bool_to_string(int value); + +/** Short name for @p rc (logging / tests). */ +const char *hybbx_result_name(hybbx_result_t rc); + +/** + * HyBBX system-local date/time formatting for logs and text/ tokens. + * Default: 24-hour clock with seconds, ISO date YYYY/MM/DD. + */ +typedef enum hybbx_date_format { + HYBBX_DATE_ISO = 0, + HYBBX_DATE_ISO_SHORT = 1, + HYBBX_DATE_US = 2, + HYBBX_DATE_EU = 3, +} hybbx_date_format_t; + +typedef struct hybbx_time_format { + int clock_12h; + int seconds; + hybbx_date_format_t date_format; +} hybbx_time_format_t; + +void hybbx_time_format_defaults(hybbx_time_format_t *fmt); +const hybbx_time_format_t *hybbx_time_format_get(void); +void hybbx_time_config_apply(const struct hybbx_config *config); + +/** Local wall-clock time for text tokens and stamps. */ +hybbx_result_t hybbx_time_local_now(struct tm *out); + +const char *hybbx_date_format_name(hybbx_date_format_t fmt); + +/** + * Format @p tm as clock time (default @c HH:MM:SS, 12h when configured). + */ +hybbx_result_t hybbx_time_format_time(char *out, size_t out_len, + const struct tm *tm, + const hybbx_time_format_t *fmt); + +/** + * Format @p tm as calendar date (default @c YYYY/MM/DD). + */ +hybbx_result_t hybbx_time_format_date(char *out, size_t out_len, + const struct tm *tm, + const hybbx_time_format_t *fmt); + +/** + * Format @p tm as @c yyyymmdd HH:MM per @p fmt (compact log stamp). + * @return HYBBX_OK on success. + */ +hybbx_result_t hybbx_time_format_stamp(char *out, size_t out_len, + const struct tm *tm, + const hybbx_time_format_t *fmt); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_UTIL_H */ diff --git a/include/hybbx/websocket.h b/include/hybbx/websocket.h new file mode 100644 index 0000000..d14e870 --- /dev/null +++ b/include/hybbx/websocket.h @@ -0,0 +1,53 @@ +#ifndef HYBBX_WEBSOCKET_H +#define HYBBX_WEBSOCKET_H + +#include "hybbx/limits.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Default WebSocket listen port (behind TLS reverse proxy). */ +#define HYBBX_WEBSOCKET_DEFAULT_PORT 4591u + +/** Default simultaneous WebSocket client connections. */ +#define HYBBX_WEBSOCKET_DEFAULT_MAX_CONNECTIONS 10u + +#define HYBBX_WEBSOCKET_BIND_V4_MAX 64 +#define HYBBX_WEBSOCKET_BIND_V6_MAX 64 +#define HYBBX_WEBSOCKET_PATH_MAX 128 + +#define HYBBX_WEBSOCKET_DEFAULT_BIND_V4 "127.0.0.1" +#define HYBBX_WEBSOCKET_DEFAULT_BIND_V6 "::1" +#define HYBBX_WEBSOCKET_DEFAULT_PATH "/hybbx" +#define HYBBX_WEBSOCKET_DEFAULT_CERT_DIR "keys" + +/** Filenames inside @c cert_dir (auto-generated on first start when TLS). */ +#define HYBBX_WS_TLS_CERT_FILENAME "hybbx_ws.crt" +#define HYBBX_WS_TLS_KEY_FILENAME "hybbx_ws.key" + +/** Self-signed WebSocket TLS certificate validity (days). */ +#define HYBBX_WS_TLS_CERT_VALID_DAYS 1825u + +typedef struct hybbx_websocket_config { + char bind_v4[HYBBX_WEBSOCKET_BIND_V4_MAX]; + char bind_v6[HYBBX_WEBSOCKET_BIND_V6_MAX]; + char path[HYBBX_WEBSOCKET_PATH_MAX]; + char cert_dir[HYBBX_PATH_MAX]; + unsigned int port; + unsigned int max_connections; + int ipv4; + int ipv6; +} hybbx_websocket_config_t; + +void hybbx_websocket_config_defaults(hybbx_websocket_config_t *config); + +hybbx_result_t hybbx_websocket_config_parse(const char *config, + hybbx_websocket_config_t *out); + +#ifdef __cplusplus +} +#endif + +#endif /* HYBBX_WEBSOCKET_H */ |
