/** ========================================================================
 * 
 * @param source
 * 
 * @return TRUE if timer has expired, FALSE if not.
 * 
 * =========================================================================
 */
static gboolean
multi_fd_watch_is_timer_expired(PslMultiFdWatchSource* source)
{
    PSL_LOG_DEBUGLOW("%s (watch=%p)", __func__, source);

    PSL_ASSERT(!source->restartTimer);

    if (MULTI_FD_WATCH_INFINITE_MSEC == source->timeoutMillisec) {
        PSL_LOG_DEBUGLOW("%s (watch=%p): using infinite timeout", __func__,
                         source);
        return false;
    }

    struct timespec now;
    if (!psl_time_get_current_mono_time(&now)) {
        return false;
    }

    if (psl_time_compare_timespecs(&now, &source->when) >= 0) {
        return true;
    }
    else {
        return false;
    }
}
/** ========================================================================
 * chan_fsm_destroy_widgets(): Called by chan_fsm_free() and the 
 * PslChanFsmFinalState's kFsmEventEnterScope event handler to 
 * destroy the "widgets" used the the FSM. 
 *  
 * @note See the note about deadlock work-around in 
 *       psl_chan_fsm_finalize() header comments.
 *  
 * @param pFsm 
 * =========================================================================
 */
static void
chan_fsm_destroy_widgets(PslChanFsm* const pFsm)
{
    PSL_LOG_INFO("%s (fsm=%p)", __func__, pFsm);

    PSL_ASSERT(pFsm);

    if (pFsm->fdWatchInfo.fdWatch) {
        /**
         * @note Multi-level GSource arrangements such as ours (a 
         *       GSource that monitors our channel instance and another
         *       GSource owned by the channel instance) are susceptible
         *       to deadlock in glib's gmain code if we're not careful.
         *       We use lots of low-level logging below to help with
         *       debugging should a deadlock be triggered.
         */

        (void)psl_multi_fd_watch_reset(pFsm->fdWatchInfo.fdWatch);

        PSL_LOG_DEBUGLOW("%s (fsm=%p): Detaching multi-fd-watch=%p",
                         __func__, pFsm, pFsm->fdWatchInfo.fdWatch);

        g_source_destroy((GSource*)pFsm->fdWatchInfo.fdWatch);

        PSL_LOG_DEBUGLOW("%s (fsm=%p): Finished detaching multi-fd-watch=%p",
                         __func__, pFsm, pFsm->fdWatchInfo.fdWatch);

        PSL_LOG_DEBUGLOW("%s (fsm=%p): Unref'ing multi-fd-watch=%p",
                         __func__, pFsm, pFsm->fdWatchInfo.fdWatch);

        g_source_unref((GSource*)pFsm->fdWatchInfo.fdWatch);

        PSL_LOG_DEBUGLOW("%s (fsm=%p): Finished unref'ing multi-fd-watch=%p",
                         __func__, pFsm, pFsm->fdWatchInfo.fdWatch);

        pFsm->fdWatchInfo.fdWatch = NULL;
    }

    g_free(pFsm->userSettings.bindAddr.hostStr);
    pFsm->userSettings.bindAddr.hostStr = NULL;
    g_free(pFsm->userSettings.serverAddr.hostStr);
    pFsm->userSettings.serverAddr.hostStr = NULL;

    if (pFsm->userSettings.threadCtx) {
        PmSockThreadCtxUnref(pFsm->userSettings.threadCtx);
        pFsm->userSettings.threadCtx = NULL;
    }

    PSL_LOG_DEBUGLOW("%s (fsm=%p): LEAVING", __func__, pFsm);
}
/** ========================================================================
 * =========================================================================
 */
static void
chan_fsm_free(PslChanFsm* const pFsm)
{
    PSL_LOG_INFO("%s (fsm=%p)", __func__, pFsm);

    PSL_ASSERT(pFsm);

    /**
     * If the FSM was successfully started, then we should have been
     * finalized by the channel via psl_chan_fsm_finalize() before 
     * our reference count dropped to zero 
     */
    PSL_ASSERT(!pFsm->fsmStarted || pFsm->fsmFinalized);

    /// Our sock should be closed by the state handlers
    if (pFsm->fd >= 0) {
        PSL_LOG_ERROR("%s: ERROR: pFsm->fd not closed!", __func__);
    }

    if (!pFsm->fsmStarted) {
        chan_fsm_destroy_widgets(pFsm);
    }

    g_free(pFsm);

    PSL_LOG_DEBUGLOW("%s (fsm=%p): LEAVING", __func__, pFsm);
}
/** ========================================================================
 * =========================================================================
 */
PslError
psl_multi_fd_watch_reset(PslMultiFdWatchSource* source)
{
    PSL_LOG_DEBUG("%s (watch=%p): ENTERING", __func__, source);

    source->restartTimer = true;
    source->timeoutMillisec = MULTI_FD_WATCH_INFINITE_MSEC;

    GHashTableIter iter;
    gpointer key, value;
    g_hash_table_iter_init(&iter, source->descTable);
    while (g_hash_table_iter_next(&iter, &key, &value)) {
        PslMultiFdDescriptor* desc = (PslMultiFdDescriptor*)value;
        const gint fd = GPOINTER_TO_INT(key);
        PSL_ASSERT(fd == desc->pollFd.fd);

        if (desc->pollAdded) {
            g_source_remove_poll((GSource*)source, &desc->pollFd);
        }
        g_hash_table_iter_remove(&iter);
    }

    PSL_LOG_DEBUGLOW("%s (watch=%p): LEAVING", __func__, source);
    return 0;
}
/** ========================================================================
 * =========================================================================
 */
PslError
psl_multi_fd_watch_remove_fd(PslMultiFdWatchSource* source, int const fd)
{
    gboolean removed;
    PslMultiFdDescriptor* desc;

    PSL_LOG_DEBUGLOW("%s (watch=%p), fd=%d", __func__, source, (int)fd);

    desc = (PslMultiFdDescriptor*) g_hash_table_lookup(source->descTable,
                                                       GINT_TO_POINTER(fd));

    if (!desc) {
        PSL_LOG_ERROR("%s (watch=%p): ERROR: fd=%d not found", __func__,
                      source, (int)fd);
        return PSL_ERR_INVAL;
    }

    if (desc->pollAdded) {
        g_source_remove_poll((GSource*)source, &desc->pollFd);
    }

    removed = g_hash_table_remove(source->descTable, GINT_TO_POINTER(fd));
    PSL_ASSERT(removed);

    return 0;
}
/** ========================================================================
 * =========================================================================
 */
int
psl_multi_fd_watch_get_fd_count(PslMultiFdWatchSource* source)
{
    PSL_LOG_DEBUGLOW("%s (watch=%p)", __func__, source);

    return g_hash_table_size (source->descTable);
}
/** ========================================================================
 * Update the timeout expiry time
 * 
 * @param source
 * 
 * @return gint: number of milliseconds that remain, or
 *         MULTI_FD_WATCH_INFINITE_MSEC if infinite
 * 
 * =========================================================================
 */
static gint
multi_fd_watch_update_expiry_time(PslMultiFdWatchSource* source)
{
    if (MULTI_FD_WATCH_INFINITE_MSEC == source->timeoutMillisec) {
        PSL_LOG_DEBUGLOW("%s (watch=%p): using infinite timeout", __func__,
                         source);

        source->restartTimer = false;
        return MULTI_FD_WATCH_INFINITE_MSEC;
    }

    gint remainingMillisec;

    if (source->restartTimer) {
        source->restartTimer = false;

        psl_time_get_current_mono_time(&source->when);
        psl_time_add_millisec_to_timespec(&source->when,
                                          source->timeoutMillisec);
        remainingMillisec = source->timeoutMillisec;
    }
    else {

        struct timespec time;
        psl_time_get_current_mono_time(&time);

        bool wouldUnderflow;

        /// 'time - when'
        psl_time_subtract_timespec_abs(&time, &source->when, &wouldUnderflow);
        /// 'time' is now the absolute value of 'time - when'
        if (wouldUnderflow) { /// expiry is still in the future
            remainingMillisec = psl_time_convert_timespec_to_millisec(&time);
        }
        else {
            remainingMillisec = 0; ///< already expired
        }

    }

    PSL_LOG_DEBUGLOW("%s (watch=%p): new mono expiry time: sec=%ld, nsec=%ld",
                     __func__, source, (long)source->when.tv_sec,
                     (long)source->when.tv_nsec);

    return remainingMillisec;

}
/** ========================================================================
 * =========================================================================
 */
PslError
psl_multi_fd_watch_add_or_update_fd(PslMultiFdWatchSource*  const source,
                                    int                     const fd,
                                    GIOCondition            const condition)
{
    PslMultiFdDescriptor* desc;

    PSL_LOG_DEBUG("%s (watch=%p): fd=%d, GIOCondition=0x%lX",
                  __func__, source, (int)fd, (unsigned long)condition);

    desc = (PslMultiFdDescriptor*) g_hash_table_lookup(source->descTable,
                                                       GINT_TO_POINTER(fd));

    /**
     * @note multi_fd_watch_prepare() takes care of adding/removing
     *       our pollFd's from the source's poll list as needed
     */

    if (desc) {
        PSL_LOG_DEBUGLOW(
            "%s (watch=%p): found desc=%p with fd=%d in descriptor table=%p",
            __func__, source, desc, (int)fd, source->descTable);

        desc->pollFd.events = condition;
    }
    else { /// create a new descriptor and add it to the poll set
        desc = g_new0(PslMultiFdDescriptor, 1);
        if (!desc) {
            PSL_LOG_FATAL("%s (watch=%p): ERROR: g_new0 failed",
                          __func__, source);
            return PSL_ERR_MEM;
        }

        desc->pollFd.fd = fd;
        desc->pollFd.events = condition;
        g_hash_table_insert(source->descTable,
                            GINT_TO_POINTER(desc->pollFd.fd),
                            desc);
        PSL_LOG_DEBUGLOW(
            "%s (watch=%p): allocated desc=%p for fd=%d in descriptor table=%p",
            __func__, source, desc, (int)fd, source->descTable);
    }

    return 0;
}
/** ========================================================================
 * =========================================================================
 */
bool
psl_inet_ipaddr_from_string(const char*         const hn,
                            PslInetIPAddress*   const pBuf,
                            const char*         const userLabel,
                            const void*         const cookie)
{
    PSL_ASSERT(pBuf);

    pBuf->family = AF_UNSPEC;

    if (!hn || !*hn) {
        PSL_LOG_ERROR("%s (%s=%p): ERROR: NULL or empty hostname string",
                      __func__, userLabel, cookie);
        return false;
    }

    int             rc;

    rc = inet_pton(AF_INET, hn, &pBuf->addr);
    if (rc > 0) {
        PSL_LOG_DEBUGLOW("%s (%s=%p): hostname looks like IPv4 address",
                         __func__, userLabel, cookie);
        pBuf->family = AF_INET;
        pBuf->len = sizeof(struct in_addr);
        return true;
    }
    else {
        rc = inet_pton(AF_INET6, hn, &pBuf->addr);
        if (rc > 0) {
            PSL_LOG_DEBUGLOW("%s (%s=%p): hostname looks like IPv6 address",
                             __func__, userLabel, cookie);
            pBuf->family = AF_INET6;
            pBuf->len = sizeof(struct in6_addr);
            return true;
        }
        else {
            PSL_LOG_DEBUGLOW("%s (%s=%p): hostname string '%s' doesn't look " \
                             "like an IP address'.",
                             __func__, userLabel, cookie, PSL_LOG_OBFUSCATE_STR(hn));
        }
    }

    return false;
}//psl_inet_ipaddr_from_string
/* =========================================================================
 * =========================================================================
 */
GMainContext*
PmSockThreadCtxPeekGMainContext(PmSockThreadContext* const ctx)
{
    PSL_ASSERT(ctx);

    PSL_LOG_DEBUGLOW("%s (ctx=%p/%s): gmainCtx=%p",
                     __func__, ctx, ctx->userLabel, ctx->gmainCtx);

    return ctx->gmainCtx;
}
/** ========================================================================
 * The 'prepare' function of GSourceFuncs
 * 
 * @param base
 * @param timeout @see GSourceFuncs
 * 
 * @return gboolean @see GSourceFuncs
 * 
 * =========================================================================
 */
static gboolean
multi_fd_watch_prepare(GSource* base, gint* timeout)
{
    PSL_LOG_DEBUGLOW("%s (watch=%p)", __func__, base);

    PslMultiFdWatchSource* const source = (PslMultiFdWatchSource*)base;

    /// Clear revents of our pollrecs
    GHashTableIter iter;
    gpointer key, value;
    g_hash_table_iter_init(&iter, source->descTable);
    while (g_hash_table_iter_next(&iter, &key, &value)) {
        PslMultiFdDescriptor* const desc = (PslMultiFdDescriptor*)value;
        desc->pollFd.revents = 0;

        if (desc->pollFd.events) {
            if (!desc->pollAdded) {
                g_source_add_poll(base, &desc->pollFd);
                desc->pollAdded = true;
            }
        }
        else if (desc->pollAdded) {
            g_source_remove_poll(base, &desc->pollFd);
            desc->pollAdded = false;
        }

        PSL_LOG_DEBUGLOW(
            "%s (watch=%p): iter: table=%p, desc=%p, fd=%d, " \
            "monitoring GIOCondition=0x%lX",
            __func__, source, source->descTable, desc, (int)desc->pollFd.fd,
            (unsigned long)desc->pollFd.events);
    }

    /// Calc remaining timeout
    *timeout = multi_fd_watch_update_expiry_time(source);

    gboolean const readyNow = (0 == *timeout);


    PSL_LOG_DEBUGLOW("%s (watch=%p): %s", __func__, base,
                     readyNow ? "READY" : "NOT READY");
    return readyNow;
}
/* =========================================================================
 * =========================================================================
 */
PmSockThreadContext*
PmSockThreadCtxRef(PmSockThreadContext* const ctx)
{
    PSL_ASSERT(ctx);

    PSL_LOG_DEBUGLOW("%s (ctx=%p/%s)", __func__, ctx, ctx->userLabel);

    psl_refcount_atomic_ref(&ctx->refCount_);

    return ctx;
}
/** ========================================================================
 * =========================================================================
 */
PslChanFsm*
psl_chan_fsm_ref(PslChanFsm* const pFsm)
{
    PSL_LOG_DEBUGLOW("%s (fsm=%p)", __func__, pFsm);

    PSL_ASSERT(pFsm);

    psl_refcount_atomic_ref(&pFsm->refCount);

    return pFsm;
}
/** ========================================================================
 * =========================================================================
 */
void
psl_chan_fsm_unref(PslChanFsm* const pFsm)
{
    PSL_LOG_DEBUGLOW("%s (fsm=%p)", __func__, pFsm);

    PSL_ASSERT(pFsm);
    PSL_ASSERT(!pFsm->base.inEvtDispatch &&
               "MUST avoid FSM run-to-completion violation");

    if (psl_refcount_atomic_unref(&pFsm->refCount)) {
        chan_fsm_free(pFsm);
    }
}
/** ========================================================================
 * The 'check' function of GSourceFuncs
 * 
 * @param base
 * 
 * @return gboolean @see GSourceFuncs
 * 
 * =========================================================================
 */
static gboolean
multi_fd_watch_check(GSource* base)
{
    PSL_LOG_DEBUGLOW("%s (watch=%p)", __func__, base);

    PslMultiFdWatchSource* const source = (PslMultiFdWatchSource*)base;

    /// If the timer is ready, there is no need to check the fd's
    if (multi_fd_watch_is_timer_expired(source)) {
        PSL_LOG_DEBUGLOW("%s (watch=%p): READY: timeout expired", __func__, base);
        return true;
    }

    /// Check if any of the file descriptors is ready
    gpointer key, value;
    GHashTableIter iter;
    g_hash_table_iter_init(&iter, source->descTable);
    while (g_hash_table_iter_next(&iter, &key, &value)) {

        const GPollFD* const pollFd = &((PslMultiFdDescriptor*)value)->pollFd;

        PSL_LOG_DEBUGLOW(
            "%s (watch=%p): iter: table=%p, desc=%p, fd=%d, " \
            "GIOCondition=(mon:0x%lX, ind:0x%lX)",
            __func__, source, source->descTable, value, (int)pollFd->fd,
            (unsigned long)pollFd->events, (unsigned long)pollFd->revents);


        if ((pollFd->revents &
             (pollFd->events | MULTI_FD_WATCH_PERM_FAILURE_IO_CONDS)) != 0) {
            PSL_LOG_DEBUGLOW("%s (watch=%p): fd=%d is READY", __func__, base,
                             (int)pollFd->fd);
            return true;
        }
    }

    PSL_LOG_DEBUGLOW("%s (watch=%p): NOT READY", __func__, base);
    return false;
}
/** ========================================================================
 * =========================================================================
 */
PslMultiFdWatchSource*
psl_multi_fd_watch_new(void)
{
    GSource* const base = g_source_new(&psl_multi_fd_watch_funcs,
                                       sizeof(PslMultiFdWatchSource));
    PslMultiFdWatchSource* const source = (PslMultiFdWatchSource*)base;

    if (!base) {
        goto error_cleanup;
    }

    /**
     * @note g_source_new returns a zero-initialized structure with
     *       the base (GSource) "class" properly set up and with
     *       reference count = 1.
     */

    source->restartTimer = true;
    source->timeoutMillisec = MULTI_FD_WATCH_INFINITE_MSEC;

    source->descTable = g_hash_table_new_full(&g_direct_hash,
                                              &g_direct_equal,
                                              NULL /*key_destroy_func*/,
                                              &g_free/*value_destroy_func*/);
    
    if (!source->descTable) {
        goto error_cleanup;
    }

    PSL_LOG_DEBUGLOW("%s (watch=%p): descriptor table=%p",
                     __func__, source, source->descTable);

    source->cachedReadyPollFdArray = g_array_new(false/*zero_terminated*/,
                                                 false/*clear_*/,
                                                 sizeof(PslMultiFdPollRec));
    if (!source->cachedReadyPollFdArray) {
        goto error_cleanup;
    }

    PSL_LOG_DEBUG("%s (watch=%p): new watch created", __func__, source);
    return source;

error_cleanup:
    PSL_LOG_FATAL("%s: FAILED (out of mem?)", __func__);
    if (base) {

        g_source_unref(base);
    }
    return NULL;
}
/* =========================================================================
 * =========================================================================
 */
void
PmSockThreadCtxUnref(PmSockThreadContext* const ctx)
{
    PSL_ASSERT(ctx);

    PSL_LOG_DEBUGLOW("%s (ctx=%p/%s)", __func__, ctx, ctx->userLabel);


    if (!psl_refcount_atomic_unref (&ctx->refCount_)) {
        return;
    }

    thread_context_destroy_internal(ctx);
}
/* =========================================================================
 * =========================================================================
 */
static void
thread_context_destroy_internal(PmSockThreadContext* const ctx)
{
    PSL_LOG_DEBUG("%s (ctx=%p/%s)", __func__, ctx,
                  PSL_LOG_MAKE_SAFE_STR(ctx->userLabel));

    PSL_ASSERT(ctx);

    g_free(ctx->userLabel);

    if (ctx->gmainCtx) {
        g_main_context_unref(ctx->gmainCtx);
    }

    g_free(ctx);


    PSL_LOG_DEBUGLOW("%s (ctx=%p): LEAVING", __func__, ctx);
}
/** ========================================================================
 * The 'dispatch' function of GSourceFuncs
 * 
 * @param base
 * @param opaqueCb @see GSourceFuncs
 * @param userData @see GSourceFuncs
 * 
 * @return gboolean @see GSourceFuncs
 * 
 * =========================================================================
 */
static gboolean
multi_fd_watch_dispatch(GSource* const base,
                        GSourceFunc opaqueCb,
                        gpointer userData)
{
    PslMultiFdWatchSource* const source = (PslMultiFdWatchSource*)base;
    PslMultiFdWatchSourceCb* const cb = (PslMultiFdWatchSourceCb*)opaqueCb;

    source->restartTimer = true; ///< so it will restart at next poll prepare
    
    if (!cb) {
        PSL_LOG_ERROR("%s (watch=%p): ERROR: multi-fd watch dispatch with " \
                      "NULL user callback ptr: did you forget to call " \
                      "g_source_set_callback()?", __func__, base);
        return false;
    }

    PSL_LOG_DEBUGLOW("%s (watch=%p): preparing to call user's callback",
                     __func__, base);

    /// Construct an array of ready PollFD's
    if (source->cachedReadyPollFdArray->len > 0) {
        g_array_set_size(source->cachedReadyPollFdArray, 0);
    }

    gpointer key, value;
    GHashTableIter iter;
    g_hash_table_iter_init(&iter, source->descTable);
    while (g_hash_table_iter_next(&iter, &key, &value)) {
        const GPollFD* const pollFd = &((PslMultiFdDescriptor*)value)->pollFd;

        PSL_LOG_DEBUGLOW(
            "%s (watch=%p): iter: table=%p, desc=%p, fd=%d, " \
            "GIOCondition=(mon:0x%lX, ind:0x%lX)",
            __func__, source, source->descTable, value, (int)pollFd->fd,
            (unsigned long)pollFd->events, (unsigned long)pollFd->revents);

        if ((pollFd->revents & MULTI_FD_WATCH_PERM_FAILURE_IO_CONDS) != 0) {
            PSL_LOG_ERROR("%s: (watch=%p): I/O FAILURE on fd=%d: indicated " \
                          "GIOCondition=0x%lX", __func__, source,
                          (int)pollFd->fd, (unsigned long)pollFd->revents);
        }


        const GIOCondition currentCondition = pollFd->revents &
            (pollFd->events | MULTI_FD_WATCH_PERM_FAILURE_IO_CONDS);
        if (currentCondition) {
            const PslMultiFdPollRec pollRec = {
                .fd = pollFd->fd,
                .reqEvents = pollFd->events,
                .indEvents = currentCondition
            };

            g_array_append_vals(source->cachedReadyPollFdArray, &pollRec, 1);
        }
    }

    /// Dispatch the callback
    const gint numrecs = source->cachedReadyPollFdArray->len;
    const PslMultiFdPollRec* const pollrecs  =
        ((numrecs > 0)
         ? (PslMultiFdPollRec*)source->cachedReadyPollFdArray->data
         : NULL);

    PSL_LOG_DEBUGLOW("%s (watch=%p): Calling user's callback: " \
                     "pollrec array=%p, numelts=%d",
                     __func__, base, pollrecs, (int)numrecs);

    const gboolean stayAttached = cb(userData, pollrecs, numrecs);

    if (!stayAttached) {
        PSL_LOG_DEBUG("%s (watch=%p): user cb requested removal of source",
                      __func__, base);
    }
    return stayAttached;
} //multi_fd_watch_dispatch



/** ========================================================================
 * The 'finalize' function of GSourceFuncs
 * 
 * @param base
 * 
 * =========================================================================
 */
static void
multi_fd_watch_finalize(GSource* base)
{
    PSL_LOG_DEBUG("%s (watch=%p)", __func__, base);

    PslMultiFdWatchSource* const source = (PslMultiFdWatchSource*)base;

    /**
     * @note We may be called with our own data members in a
     *       partially-constructed (but initialized) state if
     *       psl_multi_fd_watch_new failed part way through and
     *       called g_source_unref.
     */

    if (source->descTable) {
        /**
         * @note our "superclass" GSource takes care of removing
         *       our pollFds record pointers from the source's and the
         *       gmain context's pollFd lists when the GSource instance
         *       is being detached from the gmain context, so we don't
         *       need to do it ourselves.
         */

        g_hash_table_destroy(source->descTable);
    }

    if (source->cachedReadyPollFdArray) {
        (void)g_array_free(source->cachedReadyPollFdArray,
                           true/*free_segment*/);
    }
}
/** ========================================================================
 * =========================================================================
 */
int
psl_inet_make_sock_addr(int                       const reqFamily,
                        const char*               const addrStr,
                        int                       const port,
                        PslInetGenericSockAddr*   const res,
                        const char*               const userLabel,
                        const void*               const cookie)
{
    PSL_ASSERT(AF_INET == reqFamily || AF_INET6 == reqFamily ||
           AF_UNSPEC == reqFamily);
    PSL_ASSERT(AF_UNSPEC != reqFamily || (addrStr && addrStr[0]));
    PSL_ASSERT(res);

    memset(res, 0, sizeof(*res));

    res->family = AF_UNSPEC;


    PslInetIPAddress    pslinaddr;
    int                 inaddrlen = 0;

    int actualFamily = reqFamily;

    if (AF_UNSPEC == reqFamily) {
        PSL_LOG_DEBUGLOW("%s (%s=%p): resoloving addrfamily (was AF_UNSPEC)",
                         __func__, userLabel, cookie);
        PSL_ASSERT(addrStr);

        bool const gotAddr = psl_inet_ipaddr_from_string(addrStr, &pslinaddr,
                                                         userLabel, cookie);
        if (gotAddr) {
            actualFamily = pslinaddr.family;
            inaddrlen = pslinaddr.len;
        }
        else {
            PSL_LOG_ERROR("%s (%s=%p): ERROR: could not determine " \
                          "address family from address string '%s'.",
                          __func__, userLabel, cookie,
                          PSL_LOG_OBFUSCATE_STR(addrStr));
            return EINVAL;
        }
    }


    void*   addrDst = NULL;
    if (AF_INET == actualFamily) {
        res->addrLength = sizeof(res->sa);
        res->sa.sin_family = actualFamily;
        res->sa.sin_port = htons(port);
        res->sa.sin_addr.s_addr = INADDR_ANY;
        addrDst = &res->sa.sin_addr;
    }
    else {
        PSL_ASSERT(AF_INET6 == actualFamily);
        res->addrLength = sizeof(res->sa6);
        res->sa6.sin6_family = actualFamily;
        //res->sa6.sin6_flowinfo = 0; ///< what should we do with these?
        //res->sa6.sin6_scope_id = 0;
        res->sa6.sin6_port = htons(port);
        res->sa6.sin6_addr = in6addr_any;
        addrDst = &res->sa6.sin6_addr;
    }

    if (addrStr && inaddrlen) {
        memcpy(addrDst, &pslinaddr.addr, inaddrlen);
    }
    else if (addrStr) {
        PSL_ASSERT(addrDst);
        int const ptonRes = inet_pton(actualFamily, addrStr, addrDst);

        if (ptonRes < 0) {  /// unexpected address family - check errno
            int const saverrno = errno;
            PSL_LOG_ERROR("%s (%s=%p): ERROR: inet_pton() failed; " \
                          "family=%d, addrStr=%s, errno=%d (%s).",
                          __func__, userLabel, cookie, actualFamily,
                          PSL_LOG_OBFUSCATE_STR(addrStr), saverrno,
                          strerror(saverrno));
            PSL_ASSERT(saverrno);
            return saverrno;
        }
        else if (ptonRes == 0) {    /// addr does not match family
            /// @note This case does not set errno
            PSL_LOG_ERROR("%s (%s=%p): ERROR: inet_pton() failed; Address " \
                          "does not match family; family=%d, addrStr=%s.",
                          __func__, userLabel, cookie, actualFamily,
                          PSL_LOG_OBFUSCATE_STR(addrStr));
            return EINVAL;
        }
    }

    res->family = actualFamily;
    return 0;
}// psl_inet_make_sock_addr