Пример #1
0
/** ========================================================================
 * =========================================================================
 */
PslError
psl_inet_connect_sock(int         const s,
                      int         const addrFamily,
                      const char* const addrStr,
                      int         const port,
                      const char* const userLabel,
                      const void* const cookie)
{
    PslInetGenericSockAddr  server;

    PSL_LOG_DEBUG("%s (%s=%p): fd=%d, addrfamily=%d, addrStr=%s, port=%d",
                  __func__, userLabel, cookie, s, addrFamily,
                  PSL_LOG_OBFUSCATE_STR(addrStr), port);

    bool success = !psl_inet_make_sock_addr(addrFamily, addrStr,
                                            port, &server, userLabel, cookie);
    if (!success) {
        return PSL_ERR_BAD_SERV_ADDR;
    }

    int rc = connect(s, (struct sockaddr*)&server.sa, server.addrLength);
    PSL_ASSERT(rc <= 0);
    if (0 == rc) {
        PSL_LOG_DEBUG("%s (%s=%p): connect() succeeded immediately",
                      __func__, userLabel, cookie);
        return 0;
    }
    else if (rc < 0 && EINPROGRESS == errno) {
        PSL_LOG_DEBUG("%s (%s=%p): connect() returned EINPROGRESS",
                      __func__, userLabel, cookie);
        return 0;
    }
    /* @todo The implication of EINTR
             when connecting on a non-blocking socket is vague, at best.
             Should we retry connect(), assume that all is well, or give up
             on this connection?
    else if (rc < 0 && EINTR == errno) {
        PSL_LOG_DEBUG("%s (fsm=%p): connect() returned EINTR",
                      __func__, pFsm);
        return 0;
    }
    */
    else { /// (rc < 0)
        int const saverrno = errno;
        PSL_LOG_ERROR("%s (%s=%p): ERROR: connect() failed; errno=%d (%s)",
                      __func__, userLabel, cookie, saverrno, strerror(saverrno));

        return psl_err_pslerror_from_connect_errno(saverrno);
    }
}//psl_inet_connect_sock
Пример #2
0
/* =========================================================================
 * =========================================================================
 */
PslError
PmSockThreadCtxNewFromGMain(GMainContext*                 gmainCtx,
                            const char*             const userLabel,
                            PmSockThreadContext**   const pThreadCtx)
{
    PSL_LOG_DEBUG("%s: pThreadCtx=%p, userLabel=\"%s\", gmaincxt=%p", 
                  __func__, pThreadCtx, PSL_LOG_MAKE_SAFE_STR(userLabel),
                  gmainCtx);

    PSL_ASSERT(pThreadCtx); *pThreadCtx = NULL;

    struct PmSockThreadContext_* const ctx =
        g_new0(struct PmSockThreadContext_, 1);

    PslError    pslerr = 0;

    if (!ctx) {
        pslerr = PSL_ERR_MEM;
        goto error_cleanup;
    }

    psl_refcount_init(&ctx->refCount_, "PSL_THREAD_CTX", ctx);

    ctx->userLabel = g_strdup(userLabel ? userLabel : "PSL_user");
    if (!ctx->userLabel) {
        pslerr = PSL_ERR_MEM;
        goto error_cleanup;
    }

    gmainCtx = gmainCtx ? gmainCtx : g_main_context_default();
    ctx->gmainCtx = g_main_context_ref(gmainCtx);


    PSL_LOG_DEBUG("%s (%s): PSL Thread Context created: ctx=%p",
                  __func__, ctx->userLabel, ctx);
    *pThreadCtx = ctx;
    return 0;

error_cleanup:
    PSL_ASSERT(pslerr);
    PSL_LOG_FATAL("%s (%s): ERROR: PslError=%d (%s)", __func__,
                  PSL_LOG_MAKE_SAFE_STR(userLabel),
                  (int)pslerr, PmSockErrStringFromError(pslerr));
    if (ctx) {
        thread_context_destroy_internal(ctx);
    }
    return pslerr;
}//PmSockThreadCtxNewFromGMain
Пример #3
0
/** ========================================================================
 * =========================================================================
 */
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;
}
Пример #4
0
/** ========================================================================
 * =========================================================================
 */
PslError
psl_multi_fd_watch_set_poll_timeout(PslMultiFdWatchSource* source,
                                    int const timeoutMillisec)
{
    PSL_LOG_DEBUG("%s (watch=%p), timeout=%d millisec",
                  __func__, source, (int)timeoutMillisec);

    if (timeoutMillisec < -1) {
        PSL_LOG_ERROR("%s (watch=%p): ERROR: passed negative timemeout, " \
                      "but not -1: %d",
                      __func__, source, (int)timeoutMillisec);
        return PSL_ERR_INVAL;
    }

    source->restartTimer = true;

    if (timeoutMillisec == -1) {
        source->timeoutMillisec = MULTI_FD_WATCH_INFINITE_MSEC;
    }

    else { // timeoutMillisec >= 0
        source->timeoutMillisec = timeoutMillisec;
    }

    return 0;
}
Пример #5
0
/** ========================================================================
 * =========================================================================
 */
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
psl_chan_fsm_finalize(PslChanFsm* const pFsm)
{
    PSL_LOG_DEBUG("%s (fsm=%p)", __func__, pFsm);

    PSL_ASSERT(pFsm);

    if (pFsm->fsmStarted) {
        if (!pFsm->fsmClosed) {
            psl_chan_fsm_evt_dispatch_CLOSE(pFsm);
            PSL_ASSERT(pFsm->fsmClosed);
        }

        /// Trigger exits out of the active states to ensure orderly clean-up
        psl_chan_fsm_evt_dispatch_FINALIZE(pFsm);
    }
}///psl_chan_fsm_finalize
Пример #7
0
/** ========================================================================
 * =========================================================================
 */
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;
}
Пример #8
0
/* =========================================================================
 * =========================================================================
 */
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);
}
/** ========================================================================
 * =========================================================================
 */
static PslSmeEventStatus
chan_fsm_init_state_handler(PslChanFsmInitState*    const pState,
                            PslChanFsm*             const pFsm,
                            PslSmeEventId           const evtId,
                            const PslChanFsmEvtArg* const evtArg)
{   
    switch (evtId) 
    {
    case kFsmEventEnterScope:
    case kFsmEventExitScope:
    case kFsmEventBegin:
        return kPslSmeEventStatus_success;
        break;

    case PSL_CHAN_FSM_EVT_CONNECT:
        if (AF_UNSPEC == pFsm->userSettings.serverAddr.addrFamily && pFsm->fd < 0) {
            PSL_LOG_ERROR("%s (fsm=%p): ERROR: PSL_CHAN_FSM_EVT_CONNECT " \
                          "failed: connect address not set", __func__, pFsm);

            psl_chan_fsm_set_last_error(pFsm, kPslChanFsmErrorSource_psl,
                                        PSL_ERR_BAD_SERV_ADDR);
            return kPslSmeEventStatus_error;
        }


        if (pFsm->fd < 0) {
            psl_chan_fsm_goto_plain_lookup_state(pFsm, &evtArg->connect.data);
        }
        else {
            static const struct PslChanFsmEvtInetAddrText addrText = {
                .family = AF_UNSPEC
                };
            psl_chan_fsm_goto_plain_conn_state(pFsm, &evtArg->connect.data, &addrText);
        }
        return kPslSmeEventStatus_success;
        break;

    case PSL_CHAN_FSM_EVT_SET_CONN_FD:
        {
            const struct PslChanFsmEvtArgSetConnFD* const arg =
                &evtArg->setConnFD;

            PSL_ASSERT(arg->fd >= 0);

            if (AF_UNSPEC != pFsm->userSettings.serverAddr.addrFamily) {
                PSL_LOG_ERROR("%s (fsm=%p): SET_CONN_FD ERROR: " \
                              "not allowed when server address is set",
                              __func__, pFsm);
                return kPslSmeEventStatus_passToParent;
            }

            if (AF_UNSPEC != pFsm->userSettings.bindAddr.addrFamily) {
                PSL_LOG_ERROR("%s (fsm=%p): SET_CONN_FD ERROR: " \
                              "not allowed when bind address is set",
                              __func__, pFsm);
                return kPslSmeEventStatus_passToParent;
            }

            if (pFsm->fd >= 0) {
                PSL_LOG_ERROR("%s (fsm=%p): SET_CONN_FD ERROR: connected " \
                              "file descriptor was already set",
                              __func__, pFsm);
                return kPslSmeEventStatus_passToParent;
            }

            pFsm->fd = arg->fd;
            pFsm->userSettings.fdIsUserProvided = true;
            pFsm->userSettings.fdOpts = arg->opts;

            return kPslSmeEventStatus_success;
            break;
        }

    case PSL_CHAN_FSM_EVT_SET_SERVER:
        {
            const struct PslChanFsmEvtArgSetServer* const arg =
                &evtArg->setServer;

            if (pFsm->fd >= 0) {
                PSL_LOG_ERROR("%s (fsm=%p): SET_SERVER ADDR ERROR: not " \
                              "allowed when connected file descriptor is set",
                              __func__, pFsm);
                return kPslSmeEventStatus_passToParent;
            }

            if (AF_UNSPEC != pFsm->userSettings.serverAddr.addrFamily) {
                PSL_LOG_ERROR("%s (fsm=%p): SET_SERVER ADDR ERROR: " \
                              "server address was already set",
                              __func__, pFsm);
                return kPslSmeEventStatus_passToParent;
            }

            PSL_LOG_DEBUG("%s (fsm=%p): SET_SERVER ADDR: server: '%s:%d'/af=%d",
                          __func__, pFsm,
                          PSL_LOG_OBFUSCATE_STR(arg->hostStr),
                          (int)arg->port,
                          (int)arg->addrFamily);

            pFsm->userSettings.serverAddr.addrFamily = arg->addrFamily;
            pFsm->userSettings.serverAddr.port = arg->port;
            pFsm->userSettings.serverAddr.hostStr = g_strdup(arg->hostStr);
            PSL_ASSERT(pFsm->userSettings.serverAddr.hostStr);
        }
        return kPslSmeEventStatus_success;
        break;

    case PSL_CHAN_FSM_EVT_SET_SOCK_BIND:
        {
            const struct PslChanFsmEvtArgSetSockBind* const arg =
                &evtArg->setSockBind;

            if (pFsm->fd >= 0) {
                PSL_LOG_ERROR("%s (fsm=%p): SET_SOCK_BIND ERROR: not " \
                              "allowed when connected file descriptor is set",
                              __func__, pFsm);
                return kPslSmeEventStatus_passToParent;
            }

            if (AF_UNSPEC != pFsm->userSettings.bindAddr.addrFamily) {
                PSL_LOG_ERROR("%s (fsm=%p): SET_SOCK_BIND ERROR: " \
                              "socket bind address was already set",
                              __func__, pFsm);
                return kPslSmeEventStatus_passToParent;
            }

            PSL_LOG_DEBUG("%s (fsm=%p): SET_SOCK_BIND: local addr: '%s:%d'/af=%d",
                          __func__, pFsm,
                          PSL_LOG_MAKE_SAFE_STR(arg->ipAddrStr),
                          (int)arg->port,
                          (int)arg->addrFamily);

            pFsm->userSettings.bindAddr.addrFamily = arg->addrFamily;
            pFsm->userSettings.bindAddr.port = arg->port;
            pFsm->userSettings.bindAddr.hostStr = g_strdup(arg->ipAddrStr);
            PSL_ASSERT(!arg->ipAddrStr || pFsm->userSettings.bindAddr.hostStr);
        }
        return kPslSmeEventStatus_success;
        break;

    default:
        break; ///< allow default processing by parent
    }

    return kPslSmeEventStatus_passToParent;
}//chan_fsm_init_state_handler
/** ========================================================================
 * =========================================================================
 */
void
psl_chan_fsm_dispatch_completion_cb(
    PslChanFsm*                             const pFsm,
    const PslChanFsmEvtCompletionCbInfo*    const pCb,
    PslError                                const pslErr,
    bool                                    const switchingToCrypto)
{
    PSL_ASSERT(pFsm);
    PSL_ASSERT(pCb);

    if (pFsm->fsmClosed) {
        PSL_LOG_NOTICE("%s (fsm=%p): FSM is closed: suppressing callbacks",
                       __func__, pFsm);
        return;
    }

    enum PslChanFsmEvtCompletionCbId which = pCb->which;

    if (kPslChanFsmEvtCompletionCbId_none == which) {
        PSL_LOG_DEBUG("%s (fsm=%p): no callback to dispatch", __func__, pFsm);
        return;
    }

    /// Process external callback

    /// So we don't get destroyed while in callback
    (void)psl_chan_fsm_ref(pFsm);

    switch (which) {
    case kPslChanFsmEvtCompletionCbId_none:
        break;

    case kPslChanFsmEvtCompletionCbId_completion:
        {
            which = kPslChanFsmEvtCompletionCbId_none;

            PSL_LOG_DEBUG(
                "%s (fsm=%p/ch=%p): Dispatching 'completion' callback " \
                "to user: PslError=%d (%s)", __func__,
                pFsm, pFsm->channel, (int)pslErr,
                PmSockErrStringFromError(pslErr));

            PSL_ASSERT(pCb->u.completionCb.func);
            pCb->u.completionCb.func((PmSockIOChannel*)pFsm->channel,
                                        pFsm->userSettings.userData,
                                        pslErr);
        }
        break;

    case kPslChanFsmEvtCompletionCbId_connect: /// legacy support
        {
            which = kPslChanFsmEvtCompletionCbId_none;

            GError*     gerr = NULL;
            if (pslErr) {
                gerr = g_error_new(PmSockErrGErrorDomain(), pslErr, "%s",
                                   PmSockErrStringFromError(pslErr));
            }
            PSL_LOG_DEBUG(
                "%s (fsm=%p/ch=%p): Dispatching 'connect' callback " \
                "to legacy user: PslError=%d (%s)", __func__,
                pFsm, pFsm->channel, gerr ? (int)gerr->code : 0,
                gerr ? gerr->message : "success");

            PSL_ASSERT(pCb->u.connectCb.func);
            (void)pCb->u.connectCb.func(pFsm->channel,
                                      pCb->u.connectCb.userData, gerr);
            if (gerr) {g_error_free(gerr);}
        }
        break;

    case kPslChanFsmEvtCompletionCbId_switch: /// legacy support
        {
            which = kPslChanFsmEvtCompletionCbId_none;

            GError* gerr = NULL;
            if (pslErr) {
                gerr = g_error_new(PmSockErrGErrorDomain(), pslErr, "%s",
                                   PmSockErrStringFromError(pslErr));
            }
            PSL_LOG_DEBUG(
                "%s (fsm=%p/ch=%p): Dispatching 'security switch' callback " \
                "to legacy user: switchingToCrypto=%d, PslError code=%d (%s)",
                __func__, pFsm, pFsm->channel, switchingToCrypto,
                gerr ? (int)gerr->code : 0, gerr ? gerr->message : "success");

            PSL_ASSERT(pCb->u.switchCb.func);
            (void)pCb->u.switchCb.func(pFsm->channel, switchingToCrypto,
                                     pCb->u.switchCb.userData, gerr);
            if (gerr) {g_error_free(gerr);}
        }
        break;
    }

    PSL_ASSERT(kPslChanFsmEvtCompletionCbId_none == which);

    /// okay if the FSM gets destroyed now
    psl_chan_fsm_unref(pFsm);
    return;

}//psl_chan_fsm_dispatch_completion_cb
/** ========================================================================
 * =========================================================================
 */
PslError
psl_chan_fsm_new(PslChanFsm**           const fsmResult,
                 PmSockThreadContext*   const threadCtx,
                 PmSockOptionFlags      const options,
                 GIOChannel*            const channel,
                 const char*            const userLabel)
{
    PSL_ASSERT(fsmResult && threadCtx);

    PslChanFsm* fsm = g_new0(PslChanFsm, 1);
    PSL_ASSERT(fsm);

    /**
     * @note chan_fsm_free() logic depends on the refcount being
     *       properly initialized
     */
    psl_refcount_init(&fsm->refCount, "PSL_FSM", fsm);

    fsm->channel = channel;

    fsm->fd = PSL_CHAN_FSM_CLOSED_FD_VALUE;

    /// Init userSettings
    fsm->userSettings.bindAddr.addrFamily = AF_UNSPEC;
    fsm->userSettings.serverAddr.addrFamily = AF_UNSPEC;
    fsm->userSettings.threadCtx = PmSockThreadCtxRef(threadCtx);

    PSL_ASSERT(!(options & ~kPmSockOptFlag_allValidOpts));
    fsm->userSettings.chanOpts = options;

    /// Create a multi-fd watch source instance
    fsm->fdWatchInfo.fdWatch = psl_multi_fd_watch_new();
    PSL_ASSERT(fsm->fdWatchInfo.fdWatch);
    g_source_set_can_recurse((GSource*)fsm->fdWatchInfo.fdWatch, false);
    g_source_set_callback((GSource*)fsm->fdWatchInfo.fdWatch,
                          (GSourceFunc)&chan_fsm_fd_watch_cb,
                          fsm, NULL);
    g_source_set_priority((GSource*)fsm->fdWatchInfo.fdWatch, G_PRIORITY_HIGH);
    g_source_attach((GSource*)fsm->fdWatchInfo.fdWatch,
                    fsm->userSettings.threadCtx->gmainCtx);

    /// Set up our FSM
    psl_sme_init_machine(&fsm->base,
                         "PSL_CHAN",
                         sizeof(fsm->beginEvtArgSupport.requestArgBuf),
                         &fsm->beginEvtArgSupport.requestArgBuf,
                         &fsm->beginEvtArgSupport.dispatchArgBuf);

    FsmDbgEnableLoggingViaPmLogLib(&fsm->base.base,
                                   kFsmDbgLogOptEvents,
                                   gPslLogContext,
                                   channel/*cookie*/);


    /// Initialize and insert our common states into the FSM

    psl_sme_init_state(
        &fsm->finalState.base,
        (PslSmeStateHandlerFnType*)&chan_fsm_final_state_handler,
        "FINAL");

    psl_sme_init_state(
        &fsm->aliveState.base,
        (PslSmeStateHandlerFnType*)&chan_fsm_alive_state_handler,
        "ALIVE");

    psl_sme_init_state(
        &fsm->aliveState.initState.base,
        (PslSmeStateHandlerFnType*)&chan_fsm_init_state_handler,
        "INIT");

    psl_sme_init_state(
        &fsm->aliveState.closedState.base,
        (PslSmeStateHandlerFnType*)&chan_fsm_closed_state_handler,
        "CLOSED");


    psl_sme_insert_state(
        &fsm->base,
        &fsm->finalState.base,
        NULL/*parent*/);

    psl_sme_insert_state(
        &fsm->base,
        &fsm->aliveState.base,
        NULL/*parent*/);

    psl_sme_insert_state(
        &fsm->base,
        &fsm->aliveState.initState.base,
        &fsm->aliveState.base/*parent*/);

    psl_sme_insert_state(
        &fsm->base,
        &fsm->aliveState.closedState.base,
        &fsm->aliveState.base/*parent*/);

    /// Initialize and insert the plaintext mode states
    psl_chan_fsm_plain_init(fsm, &fsm->aliveState.base);

    /// Initialize and insert the crypto mode states
    psl_chan_fsm_crypto_init(fsm, &fsm->aliveState.base);

    /// Start the FSM at the "init" state
    psl_sme_fsm_start(&fsm->base, &fsm->aliveState.initState.base);
    fsm->fsmStarted = true;


    PSL_LOG_DEBUG("%s (fsm=%p/%s): created", __func__, fsm,
                  PSL_LOG_MAKE_SAFE_STR(userLabel));

    *fsmResult = fsm;
    return 0;
}
Пример #12
0
/** ========================================================================
 * 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*/);
    }
}