
========================================================================
## IRCd 2.11 C-to-Rust Strangler Fig Migration - Build System & FFI Seam Analysis
SUMMARY: The ircd.rs codebase is a 40k LOC IRCnet ircd 2.11 server in C spanning 40+ modules (ircd/, common/,
iauth/). The build system uses autoconf+Makefile with 5 binary targets (ircd, iauth, chkconf,
ircdwatch, mkpasswd) linked from modular .o files. Each module follows strict convention: foo.c +
foo_ext.h (public functions/globals) + foo_def.h (data structures). The shared core data lives in
common/struct_def.h. To implement a strangler fig: (1) build a Rust staticlib/cdylib and inject it
into the existing link chain, replacing individual modules' .o files one at a time while preserving
their C AB
KEY FINDINGS:
  - Public API is declaratively separated: each foo.c has foo_ext.h (extern functions/globals) + foo_def.h (struct/macro defs), making the C ABI contract explicit and FFI-friendly
  - Build graph: support/Makefile.in generates per-target object lists (IRCD_COMMON_OBJS=[bsd,dbuf,packet,send,match,parse,support], IRCD_OBJS=[channel,class,hash,ircd,list,res,s_auth,s_bsd,s_conf,s_debug,s_err,s_id,s_misc,s_numeric,s_send,s_se
  - Global state is centralized: ircd/ircd.c exports ~20 critical globals (me, client, istat, iconf, myargv, rehashed, portnum, configfile, debuglevel, bootopt, nextconnect, nextgarbage, nextping, nextdnscheck, nextexpire, ListenerLL); s_bsd.c 
  - Function export density: typical module exports 10-30 functions (send_ext.h=15 funcs, parse_ext.h=13 funcs, s_bsd_ext.h=20+ funcs, channel_ext.h=20+ funcs). All C-level command handlers are named m_* and take (aClient*, aClient*, int, char*
  - Unsafe patterns prevalent: strcpy/strcat/sprintf in s_bsd.c, malloc/free/realloc throughout. BUFSIZE=512 hardcoded, static read buffers (READBUF_SIZE=16384 in s_bsd.c). Bare socket fds in tight loops.
  - Common layer (6 modules, ~4k LOC) is reused by ircd, iauth, contrib: bsd.c (164 LOC, socket/domain ops), dbuf.c (460 LOC, dynamic buffer mgmt), match.c (365 LOC, pattern matching), packet.c (172 LOC), parse.c (972 LOC, command dispatch), se
  - Data flow is circular: ircd.c main loop calls check_pings/send_queued/read_message (all from s_bsd.c) → parse (common/parse.c) → m_* handler (s_user.c/channel.c/s_serv.c) → sendto_* (common/send.c). Central dispatch via msgtab[] array in pa
  - No Rust dependencies yet: project is pure C11/POSIX, no Cargo.toml, no existing FFI wrapper. autoconf uses @CC@, @CFLAGS@, @LDFLAGS@, IRC_ZLIB_LIBRARY, IRC_ZLIB_INCLUDE, IRC_DLIB. Compile flags differ by target (S_CFLAGS, A_CFLAGS, CC_CFLAG
RECOMMENDATIONS:
  * Strategy Phase 0: Baseline & FFI scaffolding. (a) Create ircd-sys crate with bindgen for C headers (struct_def.h, *_ext.h, *_def.h). Run bindgen once on top-level headers to auto-generate Rust bindings for all typedefs, functions, globals. 
  * Strategy Phase 1: Low-dependency leaf modules (match, packet, dbuf utilities). (a) Create ircd-match crate wrapping match.c, exporting match()/imatch()/match_esc(). Use bindgen to call into C dbuf buffer mgmt. (b) Build staticlib, add to Ma
  * Strategy Phase 2: Utility tiers (support, list, class). (a) Migrate support.c (~1182 LOC utility functions). Wrap in ircd-support crate; most functions are pure. (b) Wrap list.c (735 LOC); provide Rust-safe iterators over aClient/aChannel l
  * Strategy Phase 3: Core circular dependencies (hash, parse, send). (a) Hash tables (hash.c 1615 LOC, ~11 globals _HASHSIZE etc., 10+ public functions). Rust wrapper must safely manage global state or pass arena/handle refs. Consider keeping 
  * Strategy Phase 4: Socket I/O layer (s_bsd.c 3402 LOC, most risky). (a) DO NOT immediately rewrite. Instead: wrap timeofday/highest_fd/local[] array as a thread-safe shared state. (b) Create ircd-io crate with traits for socket ops, DNS asyn
  * Strategy Phase 5: Main event loop (ircd.c 1522 LOC). Last to migrate. Keep C ircd main(), io_loop() initially; migrate signal handlers, timing logic (nextconnect/nextgarbage calcs) to Rust incrementally. Once s_bsd I/O is Rust, flip to Rust
  * Concrete build seam: (a) Create build.rs in ircd-sys: detect system ZLIB_LIBRARY, IRC_DLIB; emit cc::Build::new().include("../ircd").include("../common").flag("-I.") & co-compile C source only if Rust module not ready. (b) In Makefile.in: a
  * FFI safety patterns: (a) Use a central CArena<'static> (TypeSafeArena or similar) for long-lived C allocations that must be safe from Rust perspective. Define transmute helpers sparingly. (b) For each module's globals, define a Rust wrapper
RISKS:
  ! Memory layout & pointer stability: C code directly embeds structs (aClient has fixed offsets). Rust repr(C) must match exactly. If configure changes struct sizes (e.g., USE_IAUTH guards extra fields),
  ! Global state initialization order: istat, iconf, poolsize (dbuf), hash tables, local[] array are initialized in ircd/ircd.c main(). Early Rust modules that reference these globals will panic if called
  ! Circular module dependencies: send.c → parse.c → s_user.c → channel.c → send.c (msgtype callbacks fire sendto_*). If Rust replaces some but not all, must provide shim layer. Mitigation: (1) migrate en
  ! Time-of-day globals (timeofday, nextconnect, nextping, etc.): s_bsd.c reads timeofday from select/poll in a tight loop. If Rust IO layer doesn't update this global at the same frequency, timers will d
  ! Unsafe socket ops (fd arrays, select/poll): highest_fd, fdas/fdaa/fdall are raw fd arrays modified in tight loops. Porting to Rust epoll/kqueue requires rewriting event multiplexing. Half-ported code 
  ! Legacy string handling (BUFSIZE=512, static char readbuf[16384]): replaced by Rust String/Vec everywhere but C code reads/writes directly to these buffers. Bounds-check violations are rampant in origi

========================================================================
## IRCnet ircd 2.11 C Codebase Module Architecture & Global State Inventory
SUMMARY: IRCnet ircd 2.11 is a 40k-LOC IRC server organized into 27 ircd/ and 9 common/ C modules. The
architecture follows a strict convention: each module foo.c has foo_ext.h (public API), foo_def.h
(struct definitions), with shared data structures in common/struct_def.h. Critical migration blocker
is the PERVASIVE MUTABLE GLOBAL STATE: singleton aClient 'me' server, globally linked client list
(aClient *client), hash tables for nick/uid/channel/sid lookup, fd-to-client array
local[MAXCONNECTIONS], global config list (aConfItem *conf), and large static buffers. These globals
are accessed ACROSS MODUL
KEY FINDINGS:
  - Singleton 'me' server client (aClient me) is kernel state: declaration in ircd.c line ~130, extern in ircd_ext.h; accessed from 30+ modules for routing/branching.
  - Global client list: aClient *client = &me (ircd.c), linked via next/prev pointers; hash tables in hash.c manage lookup by nick, uid, sid; fd array local[MAXCONNECTIONS] in s_bsd.c maps socket fds to aClient*.
  - Configuration state: aConfItem *conf (s_conf.c) is master config list, aConfItem *kconf for K-lines, *tkconf for temporary K-lines, char *networkname; accessed by s_conf, s_user, s_bsd, s_auth, and parse modules during client init & auth.
  - Hash tables: clientTable, uidTable, channelTable, sidTable, hostnameTable, ipTable (all in hash.c, file-scope static); sizes exported as _HASHSIZE, _UIDSIZE, _CHANNELHASHSIZE, _SIDSIZE; accessed every find_client/find_uid/find_channel call.
  - Channel state: aChannel *channel = NullChn (channel.c line ~50); linked list of all channels; also channel hash table in hash.c; free_link/remove_client_from_list/add_to_channel_hash_table called from 8+ modules.
  - Statistics struct: istat_t istat, *ircstp = &ircst (s_misc.c); tracks is_cl (clients), is_sv (servers), is_cbs/is_cbr (bytes), is_myclnt (local clients), etc.; read/written from s_user, s_serv, s_misc, parse.
  - Server list: aServer *svrtop (list.c); anUser *usrtop (list.c); linked via nexts/prevs; traversed by s_serv, s_misc, list, channel modules.
  - Config timers: time_t nextconnect, nextgarbage, nextping, nextdnscheck, nextexpire, nexttkexpire (ircd.c); checked in main loop (ircd.c:main), updated by s_bsd/s_misc/s_serv.
RECOMMENDATIONS:
  * Adopt 'state ownership in Rust' pattern: migrate low-level utility modules (match, dbuf, hash, support, parse) FIRST as pure Rust libraries with no global state, exporting via FFI wrappers. This builds confidence in C↔Rust FFI before touchi
  * Create Rust 'facade' modules that OWN the critical global state: e.g., a rust_client_state crate that owns aClient *client (the linked list), exposes C-callable FFI to iterate/insert/remove. Keep hash tables in Rust as implementation detail
  * Migrate s_bsd (socket I/O) and s_send (message dispatch) to Rust together, as a unit, because they are tightly coupled to dbuf and aClient.sendQ. Wrap the fd-to-client local[] array in Rust and expose safe iteration/lookup functions.
  * Leave aConfItem *conf in C for now (config reload via rehash is complex), but wrap access in FFI getters: get_conf_by_type(CONF_CLIENT), get_conf_by_host(host), etc. This allows C to remain the authoritative source while Rust modules query 
  * Migrate whowas.c (circular buffer cache) early as it is orthogonal: own the ww_index/ww_size state in Rust, export add_history/get_history as FFI, called by s_misc (exit_client) which stays in C longer.
  * Leave channel.c in C initially; channel state is deeply embedded in mode flags, member lists with Link chains, ban/exception/invite lists. Migrate to Rust AFTER core client/server logic is stable.
  * Incrementally own the timer state (nextconnect, nextping, etc.) in Rust: create a rust_timers module that owns these time_t values and exports check_timer_expired(TIMER_CONNECT) calls. Keep main loop in C, have it call Rust timer check func
  * For statistics (istat), migrate to Rust early as a read-only struct from C's perspective: Rust modules increment atomics or use interior mutability, C reads via FFI calls like get_istat_clients(), get_istat_bytes_sent(). Avoids widespread g
RISKS:
  ! CRITICAL: Shared mutable global state across all modules. aClient *client, aChannel *channel, hash tables, local[] fd array, istat struct, config list are accessed from 15+ modules simultaneously. Swa
  ! Hash table circular dependency: hash.c provides add_to_client_hash_table/del_from_client_hash_table, called by list.c (add_client_to_list), which is called by ircd.c, s_bsd.c, s_user.c. If only hash.c
  ! FD array local[MAXCONNECTIONS] is kernel state: s_bsd.c maintains highest_fd and iterates local[] in read_message (main I/O loop). If s_bsd.c is Rust-migrated but ircd.c (main loop) is C, the loop mus
  ! Dbuf sendQ/recvQ are embedded in aClient struct: every client's outgoing/incoming message queue is a dbuf. If dbuf.c is Rust-migrated but aClient stays C-allocated, then dbuf_put/dbuf_get FFI calls re
  ! Configuration reload (rehash) modifies aConfItem *conf live while clients are connected. If s_conf.c is C but other modules are Rust and cache config pointers, a rehash will invalidate Rust-side cache
  ! Whowas circular buffer is index-based (ww_index, ww_size) not pointer-based. If whowas.c is Rust-migrated but s_misc.c (exit_client caller) is C, C must call Rust's add_history(cptr, nodelay) FFI. Thi

========================================================================
## IRCnet ircd 2.11 C Codebase Mapping for Rust Strangler Migration
SUMMARY: Comprehensive analysis of 51.3k LOC IRCnet ircd 2.11 C codebase (48 .c, 65 .h files across common/,
ircd/, iauth/ directories). Mapped core data structures (aClient, aChannel, aConfItem, anUser,
aServer, aService, Link unions, dbuf queues) with their allocation patterns, reference counting
semantics, and intrusive linked-list architectures. Identified 28 major C modules with public APIs
defined in *_ext.h companion headers. Key entry points: main() → server_reboot() → event loop in
s_bsd.c with global state (client list, channel hash, config tree). Central unsafe patterns: manual
refcnt (User.
KEY FINDINGS:
  - Core global state: aClient *client (linked list root) in ircd.c:32, aChannel *channel hash root in channel.c:50, aConfItem *conf config tree in s_conf.c, aClass *classes in class.c, aServer *svrtop server tree in list.c. All globally access
  - Reference counting on User (anUser.refcnt initialized to 1 in make_user(), decremented in free_user() with loop-detection at -211001) and Server (aServer.refcnt initialized to 1). Used to track cross-module references from aClient, whowas a
  - Intrusive linked lists: aClient has next/prev/hnext pointers; aChannel has nextch/prevch/hnextch pointers; Link (SLink) union contains {cptr, chptr, aconf, alist, cp, i} with flags field to disambiguate.
  - Memory allocation: MyMalloc(size) wraps malloc(), MyFree(x) is macro 'do{if(x!=NULL) free(x); x=NULL;}while(0)' in sys_def.h. No arena/pool allocators; freelists checked only during client creation (make_client).
  - Hash tables: HASHSIZE=MAXCONNECTIONS*1.75 for client/nick hash; CHANNELHASHSIZE=HASHSIZE/2; SIDSIZE=MAXCONNECTIONS/10; UIDSIZE=HASHSIZE. Dynamic growth via reallocation in inithashtables().
  - Lifetime complexity: aClient struct sized conditionally (CLIENT_LOCAL_SIZE vs CLIENT_REMOTE_SIZE). Local clients store count, buffer[512], sendQ/recvQ (dbuf), socket fd, auth state. Remote clients omit local fields, dereferenced only via pa
  - dbuf queue structure: dynamic buffer (dbuf.length, dbuf.offset, dbuf.head/tail pointers to dbufbuf chains of 2032-byte nodes). Used for sendQ/recvQ on every aClient. No memory limit per queue, subject to configuration MaxSendq.
  - Event loop: single-threaded select() in s_bsd.c read_message() at ~line 2055 (for(res=0;;)). Per-client state machines: STAT_UNKNOWN→STAT_CONNECTING→STAT_HANDSHAKE→STAT_CLIENT/STAT_SERVER/STAT_OPER. No async I/O or thread pools.
RECOMMENDATIONS:
  * Phase 1 - FFI Boundary Definition: Retain aClient, aChannel, aUser, aServer, aService as #[repr(C)] opaque types. Keep all struct layouts untouched initially. Implement Rust wrapper types (e.g., ClientHandle, ChannelHandle) wrapping *mut aC
  * Phase 2 - Incremental Module Replacement: Target smallest leaf modules first (e.g., class.c→ rust class module, then hash.c with class dependency). For each module: write Rust equivalents of all *_ext.h public functions, test compatibility 
  * Refcnt Migration Strategy: User/Server refcnt fields remain in C struct. Rust layer can use Arc<Mutex<>> or Rc<RefCell<>> only for new Rust-allocated objects. Initially, keep manual refcnt semantics in C layer; later, implement deref coerci
  * Hash Table Rewrite: Rust HashMap/BTreeMap for client nick/uid/server sid lookups. Wrap aClient pointers with NonNull + PhantomData<&'static aClient>. Collision handling: replace open-addressing (current) with separate chaining (Rust vec of 
  * Link/Union Polymorphism: Replace Link union with Rust enum (LinkValue::ClientPtr(*mut aClient) | LinkValue::ChannelPtr(*mut aChannel) | LinkValue::ConfPtr(*mut aConfItem) | ...). Use NonNull for pointer variants. Implement find_user_link/fi
  * dbuf Queue Refactor: Implement Rust VecDeque<u8> for sendQ/recvQ initially (bounds-checked, no overflow). Later: custom Ring buffer with configurable MaxSendq limits per client, dropping old messages if limit exceeded. Decouple from aClient
  * Memory Arena Option: Use bumpalo or typed_arena for temporary allocations (short-lived Links, config parsing). Keep malloc for long-lived aClient/aChannel/aServer (C layer manages). Migrate to Rust after C layer fully isolated.
  * Socket Descriptor Safety: Replace bare int fd with wrapper Fd(RawFd) type. Impl Drop to close(2) on double-free protection. Keep fd=-1 convention but validate at all access sites. Migrate s_bsd.c event loop to async Tokio or mio after core 
RISKS:
  ! Intrusive Lists Aliasing Danger: aClient next/prev/hnext pointers + aChannel nextch/prevch/hnextch form doubly-linked lists with mutable global root. Rust borrow checker will reject &mut client traver
  ! Reference Counting Bugs: User/Server refcnt can leak (forgotten free_user/free_server call), double-free (refcnt goes negative, caught at -211001 sentinel but corrupt state persists), or ABA problem (
  ! Union Polymorphism Type Safety: Link.value union allows writing cptr and reading as chptr (no discriminant check). Refcnt code may dereference wrong type if caller misses flags field. Rust enum varian
  ! Pointer Invalidation on Realloc: Hash table growth via realloc() can move aHashEntry[] to new address. If Rust code holds pointer to old aHashEntry[i], subsequent lookups access freed memory. Mitigati
  ! Socket Descriptor Reuse: close(fd) + new accept() can reuse same fd. aClient.fd=-1 after close, but stale pointers in fdas.fd[] can alias. Accessing local[old_fd] in race-free code is unsafe. Migrate 
  ! Configuration Reload Race: rehash() rebuilds conf tree while servers may be attaching/detaching configs (attach_conf/detach_conf called by s_user.c handlers). List iteration is NOT atomic. Rust: use A

========================================================================
## IRCnet ircd 2.11 C Codebase Architecture & Safety Analysis for C->Rust Strangler Migration
SUMMARY: 39,912 LOC across 45 files in a hierarchical IRC daemon with clear module boundaries defined by
_ext.h public API contracts. Core infrastructure: fixed-size buffers (BUFSIZE=512, HOSTLEN=63,
NICKLEN=15), dynamic message queues (dbuf), signal-driven I/O event loop. Major safety hazards: 230
strcpy/strcat/sprintf calls, manual string truncation via strncpyzt(), network input parsing
(parse.c, s_user.c, s_serv.c, channel.c) touching untrusted data, resolver DNS handling (res.c 1779
LOC), incomplete snprintf migration, minimal null-termination guarantees. Single goto-based cleanup
pattern in ircd.
KEY FINDINGS:
  - Strict buffer size contracts: BUFSIZE (512 bytes), HOSTLEN (63), NICKLEN (15), USERLEN (10), TOPICLEN (255), PASSWDLEN (20), BANLEN (~73 bytes), CHIDLEN (5), SIDLEN (4) are network protocol invariants—cannot change during migration
  - Fixed-size struct buffers embedded in aClient (BUFSIZE buffer at offset ~537 for local clients), aChannel (TOPICLEN+1), aServer (HOSTLEN+1 namebuf), aUser (HOSTLEN+1 host, USERLEN+1 username, UIDLEN+1 uid)
  - Legacy string functions: 230 strcpy/strcat/sprintf calls (irc_sprintf custom variant with BUFSIZE hard limit), 111 strncpy/snprintf calls, strncpyzt() macro for bounded copies without full-length enforcement
  - Custom irc_sprintf() (common/irc_sprintf.c) handles vaarg formatting with built-in BUFSIZE assertion—assertion-based safety not suitable for production Rust boundary
  - Network parsing entrypoints: parse.c (m_message, parse buffer reassembly), s_user.c (m_nick, m_user, m_private, m_notice—untrusted input from clients), s_serv.c (m_server, m_njoin), channel.c (m_join, m_part, m_mode, m_topic)
  - Resolver subsystem (res.c 1779 LOC + res_comp.c 854 + res_init.c + res_mkquery.c): manual hostname/alias buffer management, strcpy chains (res.c:689,697,736,780), classic libresolv patterns
  - Signal handling: setup_signals() in ircd.c:1337 sets SIGHUP/SIGUSR1/SIGCHLD/SIGPIPE/SIGALRM/SIGTERM/SIGINT with minimal async-signal-safe operations; s_die(), s_restart(), s_rehash() handlers touch global state (unsafe)
  - Dynamic buffer pools: dbuf_* (common/dbuf.c, dbuf.h) manages sendQ/recvQ message queues per client; dbuf_put/dbuf_get/dbuf_copy, poolsize global state, freelist management
RECOMMENDATIONS:
  * PHASED STRANGLER APPROACH: Target modules in order of (1) isolation from network input, (2) code complexity, (3) external visibility. Proposed order: irc_sprintf.c (custom formatting), dbuf.c (buffer pools), hash.c (lookups), match.c (patte
  * FFI BOUNDARY DESIGN: Each Rust module must expose a C ABI wrapper matching the corresponding _ext.h exactly. Use cbindgen to generate Rust FFI declarations from _ext.h. Preserve struct layouts (repr(C)) for embedded buffers in aClient/aChan
  * BUFFER SAFETY TEST SUITE: Write harness tests for all 13 fixed-size limits (BUFSIZE, HOSTLEN, NICKLEN, USERLEN, TOPICLEN, CHANNELLEN, PASSWDLEN, KEYLEN, BANLEN, CHIDLEN, SIDLEN, REALLEN, MAXBANS). Test that: (1) each buffer receives exact-l
  * PARSER FUZZING FOCUS: parse.c processes untrusted network input. Before Rust rewrite, establish fuzzing baseline with libFuzzer on m_message, m_nick, m_user, m_join, m_part, m_mode, m_topic with pathological inputs: 512-byte buffers, max ni
  * NETWORK INPUT ENTRY POINTS: Identify all untrusted input sources for Rust boundary safety: (1) s_bsd.c read_message() fills aClient.buffer[BUFSIZE] from socket—wrap with length validation before calling Rust parse(), (2) parse.c parv[] arra
  * SIGNAL SAFETY AUDIT: Current handlers (s_die, s_restart, s_rehash in ircd.c:1337) are not async-signal-safe (they modify global state without atomics). Rust version must use atomic flags (e.g., std::sync::atomic::AtomicBool) set in handlers
  * GLOBAL STATE REFACTORING: The 30+ global extern variables require careful initialization order and thread-safety (future). Create a global Config/State struct that is initialized once at startup, then frozen. Use Arc<Mutex<>> or similar for
  * MEMORY REFCOUNTING PROTOCOL: aUser/aServer/aService have refcnt fields shared across modules (anUser referenced from aClient.user, aServer, whowas[], aService.servp, etc.). Document the exact refcounting invariants: when to increment (user 
RISKS:
  ! BUFFER OVERFLOW HOTSPOTS: (1) parse.c reassembly state machine—if client sends partial lines, buffer pointer math could overflow if not carefully bounded, (2) s_user.c nickname/username copying with s
  ! SIGNAL HANDLER SAFETY: Handlers (s_die, s_restart, s_rehash) set static flags and may call malloc/free inside handlers—classic async-signal-safety violation. Can crash during high I/O load if signal a
  ! GLOBAL STATE MUTATIONS: 30+ global variables modified across multiple modules without synchronization. Example: conf linked list modified during REHASH while threads might be reading it. Future multi-
  ! REFCOUNTING BUGS: aUser/aServer with refcnt fields are hard to reason about. If a module forgets to increment on new reference or double-free, clients may disappear prematurely or dangling pointers pe
  ! DBUF POOL EXHAUSTION: If sendQ or recvQ bloats (malicious clients spam), dbuf_put may fail but return status might be ignored. No global limit enforced—OOM killer might trigger. Rust version should ha
  ! HASH COLLISION DENIAL: inithashtables() sets size at runtime, but hash function is hidden. If an attacker crafts nicks to collide, lookups degrade to O(n). No explicit collision chains or separate cha

========================================================================
## IRCnet ircd 2.11 C Codebase Analysis for Strangler Fig C->Rust Migration
SUMMARY: Comprehensive mapping of IRCnet ircd 2.11 (~51k LOC) across 51 C source files organized into 4
subsystems: common utilities (9 modules, ~5.8k LOC), core ircd daemon (39 modules, ~34k LOC), iauth
authentication daemon (6 modules, ~3k LOC), and build infrastructure. The codebase follows a strict
convention of *_ext.h public API headers (41 found) declaring 417 extern functions/variables.
Minimal test infrastructure exists (only clang-format CI); no unit tests discovered. Ready for
strangler fig migration starting with leaf modules (match, dbuf, irc_sprintf, hash) progressing to
core (parse, send
KEY FINDINGS:
  - 41 *_ext.h public API headers expose 417 extern function/variable declarations across modules
  - Global state concentrated in: ircd.c (me, client, myargv, configfile, rehashed, portnum), s_bsd.c (FdAry fdas/fdaa/fdall, local[], highest_fd, timeofday), s_conf.c (conf, kconf, networkname), hash.c (_HASHSIZE, _UIDSIZE, _CHANNELHASHSIZE, _
  - No existing unit tests; only static analysis CI (clang-format). Build infrastructure: autoconf with setup.h.in compile-time configuration.
  - Core data structures immutable: aClient (client/user/server), aChannel, anUser, aServer, aConfItem, aZdata, FdAry - stable across migration
  - Protocol compliance can be verified against 5 RFC documents in doc/: RFC1459 (core), RFC2810-2813 (server/channels/modes), WHOX (extended WHO), WHOISTLS (TLS info)
  - Module interdependencies form a DAG: leaf (match, dbuf) -> intermediate (parse, send, hash) -> core (s_conf, s_bsd, ircd.c)
  - Configuration parsing (s_conf.c) and iauth integration (s_auth.c) are critical control points for strangler fig junctures
  - No dynamic memory profiling or sanitizer instrumentation in existing build; recommend AddressSanitizer for C -> Rust boundary testing
RECOMMENDATIONS:
  * STRANGLER FIG PHASE 1 (Leaf Utilities) - Pure Functions, No External State: Migrate common/{match.c, dbuf.c, irc_sprintf.c, support.c} to Rust behind C ABI. These modules export <40 functions total, zero global mutable state (except static 
  * STRANGLER FIG PHASE 2 (Core Data & Lookup) - Immutable-First: Migrate ircd/{hash.c, list.c, class.c, channel.c} to Rust. These manage hash tables and lookups but have limited mutation patterns. Wrap in Rust-safe abstractions for _HASHSIZE, 
  * STRANGLER FIG PHASE 3 (Parser & Message Transport) - State at Boundary: Migrate common/{parse.c, send.c}, ircd/s_numeric.c, s_err.c. Keep message tables (msgtab[], replies[]) as shared read-only C arrays. Wrap parse() in Rust to handle stru
  * STRANGLER FIG PHASE 4 (Critical Control Points) - Boundary Design: Migrate ircd/{s_conf.c, s_auth.c, s_bsd.c, ircd.c} last. These touch global state (conf, iauth_options, FdAry, timeofday). Design Rust wrapper layer handling:  (1) aConfItem
  * TEST STRATEGY LAYER 1 - Leaf Unit Tests: For each Rust module, write table-driven Go tests (use xe-go:go-table-driven-tests) comparing C vs Rust outputs on 50+ test cases per function (match with wildcards, dbuf edge cases, sprintf format s
  * TEST STRATEGY LAYER 2 - Integration/Golden Testing: Compile both C and Rust versions, run identically configured ircd instances side-by-side on separate ports (6667-C, 6668-Rust) with same ircd.conf. Script IRC client to connect to both, ex
  * TEST STRATEGY LAYER 3 - Differential Fuzzing: Generate random IRC protocol inputs (malformed commands, oversized parameters, UTF-8 edge cases) via libfuzzer or AFL. Fuzz both parse() implementations with identical seed, compare error states
  * TEST STRATEGY LAYER 4 - Conformance Testing: Run against RFC compliance test vectors (from rfc2812.txt EBNF). For each numeric (001-530), verify Rust-migrated handlers emit identical format/parameters. Use doc/{whox.md, whoistls.txt} for ex
RISKS:
  ! RISK: Global state mutations in ircd.c (me, client, myargv, rehashed flags) are interdependent; premature Rust migration of s_bsd/s_conf without Rust-native mutability model will create data races. MI
  ! RISK: parse.c's msgtab[] static array (struct Message[]) is shared across modules; changing dispatch mechanism will break existing command handlers. MITIGATION: Keep msgtab in C, wrap individual handl
  ! RISK: s_bsd.c manages 3 parallel FdAry structs (fdas, fdaa, fdall) and a local[] client array with tight synchronization (poll/select loops). Rust's borrow checker will force redesign. MITIGATION: Lea
  ! RISK: iauth protocol (s_auth.c -> iauth daemon) uses bidirectional message pipes; Rust async/await will change latency profile. MITIGATION: Keep iauth I/O in C until full s_auth.c migration; bridge vi
  ! RISK: Configuration reload (rehashed flag, s_conf.c re-parse on SIGHUP) requires atomic state swap across multiple modules (classes, conf, kconf). Partial Rust migration will complicate consistency. M
  ! RISK: Memory layout assumptions (aClient offset calculations, dbuf pool fragmentation, hash bucket sizing) are implicit in C code. Rust-side changes (alignment, padding) will silently corrupt shared m

========================================================================
## IRCnet ircd 2.11 C Codebase: External Dependencies & Platform Abstraction Layer Analysis
SUMMARY: IRCnet ircd 2.11 is a ~48 C source files + 9 common utility modules (~51k LOC) system with
select/poll-based event loop and bundled DNS resolver. Critical external dependencies include: zlib
(compression, optional via USE_ZLIB), libc crypt() (password hashing), platform-specific resolver
(gethostbyname), and dynamic module loading (dlopen/dlsym via dlfcn.h for iauth DSM). The codebase
is heavily abstracted for POSIX portability via common/os.h and sys_def.h macros, supporting Unix
variants (Linux, FreeBSD, Solaris, HPUX, AIX, SGI). IPv6 is supported via AF_INET6 socket
abstraction. Key platfor
KEY FINDINGS:
  - Compression: zlib (libz) bundled via s_zip.c (ircd/s_zip.c, ZIP_LINKS feature), runtime version checked at startup in ircd/ircd.c; optional build-time flag USE_ZLIB
  - DNS Resolver: Bundled BIND-compatible resolver (ircd/res.c, res_comp.c, res_init.c, res_mkquery.c) with own cache (res_def.h: ARR_TTL=600s, ARES_CACSIZE=1009, MAXCACHED=512); uses gethostbyname/getaddrinfo abstractions; IPv6 AAAA support in
  - Event Loop: Dual select()/poll() implementation in ircd/s_bsd.c (lines 2209-2223); conditional USE_POLL; SELECT_FDSET_TYPE abstraction in os.h; FdAry structures for fd management; FIFOread/write callbacks
  - Dynamic Module Loading: iauth (isolation auth daemon) uses dlopen/dlsym in iauth/a_conf.c with DLSYM_NEEDS_UNDERSCORE macro (os.h line 236) for BSD non-ELF systems; modules loaded at runtime via MODULE stanza in config
  - Platform Abstraction: common/os.h (725 lines) centralizes all platform detection (Linux, BSD, Solaris, HPUX, AIX, SGI, NEXT); configures ACCEPT_TYPE_ARG3, SOCK_LEN_TYPE, SELECT_FDSET_TYPE; memory/string compat shims (bzero→memset, bcopy→mem
  - IPv6 Support: AF_INET6 default (AFINET macro in os.h line 680); IN_ADDR/SOCKADDR_IN mapped to in6_addr/sockaddr_in6; INET6_ADDRSTRLEN=46; inetpton/inetntop used; resolv_def.h RES_USE_INET6 flag
  - Signal Handling: POSIX signals (sigaction) with BSD_RELIABLE_SIGNALS or POSIX_SIGNALS; registered handlers: SIGINT, SIGTERM, SIGUSR2 (s_log toggle), SIGPIPE, SIGALRM in iauth and ircd startup
  - Password Hashing: System crypt() function (contrib/mkpasswd/mkpasswd.c); MD5/DES/Blowfish/ExtDES support via salt prefix; no bcrypt or modern KDF—vulnerable pattern
RECOMMENDATIONS:
  * Compression: Replace zlib via flate2 crate with rustls integration; extract s_zip.c logic into a zip_layer module that wraps flate2 Encoder/Decoder, maintain aZdata compatibility via FFI struct
  * DNS Resolver: Migrate to hickory-dns (async-friendly, pure Rust) replacing bundled BIND resolver; create dns_cache module with Arc<RwLock<LruCache>> (1009 entry limit, 600s TTL), implement res_ext.h public API via stub FFI layer until resol
  * Event Loop: Use tokio (async runtime) or mio (raw event loop) replacing select/poll; tokio preferred for cleaner async/await syntax; wrap existing FdAry poll logic in a tokio::select! macro, maintain backward compat with read_message() dela
  * Dynamic Modules: Migrate iauth DSM (dlfcn dlopen/dlsym) to libloading crate with __underscore name mangling for BSD; define a stable Rust trait (Module::init/cleanup/run) that C FFI callbacks invoke; start with mod_dnsbl.c as first iauth mo
  * Signal Handling: Use nix::signal (sigaction wrapper) or signal-hook crate; define signal handlers as static atomics (AtomicBool) for SIGUSR2 log toggle, SIGTERM graceful shutdown; avoid async-signal-safe restrictions by deferring work to ma
  * IPv6 Support: Keep AF_INET6 abstraction but validate all in6_addr struct assumptions match Rust std::net::Ipv6Addr memory layout (16 bytes); use libc::in6_addr bindings; test IPv4-mapped IPv6 addresses and ::ffff:192.0.2.1 notation
  * Password Hashing: DO NOT migrate crypt() to bcrypt/argon2 until full C↔Rust FFI for password validation is defined; for new builds, use password-hash crate (PHC) with argon2id; maintain crypt() wrapper for legacy oper password checking
  * Non-blocking I/O: Replace fcntl(F_SETFL, O_NONBLOCK) with Rust std::os::unix::io::AsRawFd + nix::fcntl; consolidate all socket setup in a set_nonblocking(fd: i32) Rust function, call from C via FFI
RISKS:
  ! ZLIB_VERSION_MISMATCH: ircd.c startup checks zlib_version major/minor/full string; Rust flate2 may have different version reporting—FFI wrapper must match C expectations or runtime will crash. MITIGAT
  ! DNS_CACHE_INVALIDATION: Bundled resolver cache uses TTL-based expiry (res_def.h AR_TTL=600s); hickory-dns uses different TTL handling—stale cache entries could cause auth failures. MITIGATION: Test hi
  ! SELECT_POLL_SEMANTICS: select()/poll() have subtle differences (e.g., poll fd=-1 ignored, select FD_SETSIZE limit); tokio abstracts both but error handling differs (e.g., EAGAIN vs EWOULDBLOCK). MITIG
  ! SIGNAL_ASYNC_SAFETY: SIGUSR2 currently toggles debug logging inline in signal handler; Rust static atomics + event loop approach is safer but requires refactoring log level check on every message. MIT
  ! MODULE_SYMBOL_MANGLING: DLSYM_NEEDS_UNDERSCORE macro (os.h line 236) for BSD non-ELF systems prepends '_' to symbol names; libloading doesn't do this automatically—iauth DSM modules may fail to load o
  ! CRYPT_BACKWARD_COMPAT: System crypt() produces implementation-specific hashes (glibc MD5/DES, OpenSSL bcrypt); switching to Rust bcrypt will invalidate existing oper passwords. MITIGATION: Dual-valida
