예제 #1
0
static void releaseSdrAcsSignal(Object signalLElt)
{
    Sdr			bpSdr = getIonsdr();
    Sdr                 acsSdr = getAcssdr();
    Object              signalAddr;
    SdrAcsSignal        signal;
    SdrAcsPendingCust   pendingCust;

    assert(signalLElt != 0);
    ASSERT_ACSSDR_XN;
    ASSERT_BPSDR_XN;

    if (acsSdr == NULL)
    {
        putErrmsg("Can't release ACS, SDR not available.", NULL);
        return;
    }

    signalAddr = sdr_list_data(acsSdr, signalLElt);
    if (signalAddr == 0) {
        ACSLOG_ERROR("Can't derefence ACS signal to release it.");
        return;
    }

    sdr_peek(acsSdr, signal, signalAddr);
    sdr_peek(acsSdr, pendingCust, signal.pendingCustAddr);

    /* Destroy the objects this AcsSignal contains */
    sdr_list_destroy(acsSdr, signal.acsFills, releaseSdrAcsFill, NULL);

    if(signal.acsDue != 0) {
        destroyBpTimelineEvent(signal.acsDue);
    }

    if(signal.serializedZco != 0) {
        zco_destroy(bpSdr, signal.serializedZco);
    }

    /* Destroy this AcsSignal */
    sdr_free(acsSdr, signalAddr);
    sdr_list_delete(acsSdr, signalLElt, NULL, NULL);
}
예제 #2
0
int trySendAcs(SdrAcsPendingCust *custodian,
               BpCtReason reasonCode, unsigned char succeeded,
               const CtebScratchpad *cteb)
{
    Object				signalLElt;
    Object				signalAddr;
    SdrAcsSignal        signal;
    BpEvent				timelineEvent;
    Object				newSerializedZco;
    unsigned long		newSerializedLength;
    int					result;
    Sdr					bpSdr = getIonsdr();

    /* To prevent deadlock, take bpSdr before acsSdr. */
    CHKERR(sdr_begin_xn(bpSdr));
    CHKERR(sdr_begin_xn(acsSdr));

    signalLElt = findSdrAcsSignal(custodian->signals, reasonCode, succeeded,
                                  &signalAddr);
    if (signalAddr == 0)
    {
        ACSLOG_ERROR("Can't find ACS signal");
        sdr_exit_xn(acsSdr);
        sdr_exit_xn(bpSdr);
        return -1;
    }
    sdr_peek(acsSdr, signal, signalAddr);


    newSerializedLength = serializeAcs(signalAddr, &newSerializedZco,
                                       signal.serializedZcoLength);
    if (newSerializedLength == 0)
    {
        ACSLOG_ERROR("Can't serialize new ACS (%lu)", signal.serializedZcoLength);
        sdr_cancel_xn(acsSdr);
        sdr_cancel_xn(bpSdr);
        return -1;
    }
    ACSLOG_DEBUG("Serialized a new ACS to %s that is %lu long (old: %lu)",
                 custodian->eid, newSerializedLength, signal.serializedZcoLength);


    /* If serializeAcs() (which serializes an ACS that covers all the "old"
     * custody IDs as well as 1 "new" custody ID that we're trying to append)
     * returned an ACS that's larger than the custodian' preferred size, then:
     *  1) Send the old ACS (the biggest ACS that's smaller than custodian's
     *     preferred size), covering all the old custody IDs but not the new
     *     one.
     *  2) Make a new ACS that includes only the new custody ID.
     */
    if (custodian->acsSize > 0 && newSerializedLength >= custodian->acsSize)
    {
        if(signal.serializedZco == 0)
        {
            /* We don't have an old unserialized ACS to send.  This means the
             * first custody signal appended to this ACS exceeded the acsSize
             * parameter.  The best we can do is send this ACS even though it's
             * bigger than the recommended acsSize. */
            ACSLOG_WARN("Appending first CS to %s was bigger than %lu",
                        custodian->eid, custodian->acsSize);
            signal.serializedZcoLength = newSerializedLength;
            signal.serializedZco = newSerializedZco;
            sdr_poke(acsSdr, signalAddr, signal);
            sendAcs(signalLElt);
            if(sdr_end_xn(acsSdr) < 0)
            {
                ACSLOG_ERROR("Can't serialize ACS bundle.");
                sdr_cancel_xn(bpSdr);
                return -1;
            }
            if (sdr_end_xn(bpSdr) < 0)
            {
                ACSLOG_ERROR("Can't send ACS bundle.");
                return -1;
            }
            return 0;
        }

        /* Calling this invalidates our signalLElt and signalAddr pointers, so
         * we must re-find the signal before using them again. */
        sendAcs(signalLElt);

        /* Add the one that was uncovered by the serialized payload back in */
        result = appendToSdrAcsSignals(custodian->signals,
                                       signal.pendingCustAddr, reasonCode, succeeded,
                                       cteb);
        switch (result)
        {
        case 0:
            /* Success; continue processing. */
            break;
        default:
            ACSLOG_ERROR("Can't carry size-limited ID to new ACS");
            sdr_cancel_xn(acsSdr);
            sdr_cancel_xn(bpSdr);
            return -1;
        }

        /* Find the uncovered one that we just added. */
        signalLElt = findSdrAcsSignal(custodian->signals, reasonCode,
                                      succeeded, &signalAddr);
        if (signalAddr == 0)
        {
            ACSLOG_ERROR("Can't find ACS signal");
            sdr_cancel_xn(acsSdr);
            sdr_cancel_xn(bpSdr);
            return -1;
        }
        sdr_peek(acsSdr, signal, signalAddr);

        /* Serialize the new one */
        newSerializedLength = serializeAcs(signalAddr, &newSerializedZco, 0);
        if (newSerializedLength <= 0)
        {
            ACSLOG_ERROR("Can't serialize new ACS (%lu)", newSerializedLength);
            sdr_cancel_xn(acsSdr);
            sdr_cancel_xn(bpSdr);
            return -1;
        }
    } else {
        if (signal.serializedZco != 0)
        {
            /* Free the old payload zco. */
            zco_destroy(bpSdr, signal.serializedZco);
        }
    }

    /* Store the new ZCO */
    signal.serializedZco = newSerializedZco;
    signal.serializedZcoLength = newSerializedLength;

    /* If there is not an ACS generation countdown timer, create one. */
    if(signal.acsDue == 0)
    {
        timelineEvent.type = csDue;
        if(custodian->acsDelay == 0) {
            timelineEvent.time = getUTCTime() + DEFAULT_ACS_DELAY;
        } else {
            timelineEvent.time = getUTCTime() + custodian->acsDelay;
        }
        timelineEvent.ref  = signalLElt;
        signal.acsDue = insertBpTimelineEvent(&timelineEvent);
        if (signal.acsDue == 0)
        {
            ACSLOG_ERROR("Can't add timeline event to generate ACS");
            sdr_cancel_xn(acsSdr);
            sdr_cancel_xn(bpSdr);
            return -1;
        }
    }
    sdr_poke(acsSdr, signalAddr, signal);
    if(sdr_end_xn(acsSdr) < 0)
    {
        ACSLOG_ERROR("Can't track ACS");
        sdr_cancel_xn(bpSdr);
        return -1;
    }
    if (sdr_end_xn(bpSdr) < 0)
    {
        ACSLOG_ERROR("Can't add timeline event to generate ACS");
        return -1;
    }
    return 0;
}
예제 #3
0
int sendAcs(Object signalLElt)
{
    BpExtendedCOS		ecos = { 0, 0, 255 };
    Object			signalAddr;
    Object			acsBundleObj;	/* Unused write-out of bpSend */
    SdrAcsSignal        	signal;
    SdrAcsPendingCust	pendingCust;
    int			result;
    Sdr			bpSdr = getIonsdr();

    assert(signalLElt != 0);

    if ((acsSdr = getAcssdr()) == NULL)
    {
        putErrmsg("Can't send ACS, SDR not available.", NULL);
        return -1;
    }

    /* To prevent deadlock, we take the BP SDR before the ACS SDR. */
    CHKERR(sdr_begin_xn(bpSdr));
    CHKERR(sdr_begin_xn(acsSdr));

    signalAddr = sdr_list_data(acsSdr, signalLElt);
    if (signalAddr == 0) {
        ACSLOG_ERROR("Can't derefence ACS signal to send it.");
        sdr_cancel_xn(acsSdr);
        sdr_cancel_xn(bpSdr);
        return -1;
    }

    sdr_peek(acsSdr, signal, signalAddr);
    sdr_peek(acsSdr, pendingCust, signal.pendingCustAddr);

    /* Remove ref to this serialized ZCO from signal; also remove the bundle
     * IDs covered by this serialized ZCO. */
    result = bpSend(NULL, pendingCust.eid, NULL, ACS_TTL,
                    BP_EXPEDITED_PRIORITY, NoCustodyRequested, 0, 0, &ecos,
                    signal.serializedZco, &acsBundleObj, BP_CUSTODY_SIGNAL);
    switch (result)
    {
    /* All return codes from bpSend() still cause us to continue processing
     * to free this ACS.  If it was sent successfully, good.  If it wasn't,
     * that's due to a system failure or problem with this ACS, so the best
     * we can do is delete it from our node without sending. */
    case -1:
        ACSLOG_ERROR("Can't send custody transfer signal.");
        zco_destroy(bpSdr, signal.serializedZco);
        break;

    case 0:
        ACSLOG_ERROR("Custody transfer signal not transmitted.");
        zco_destroy(bpSdr, signal.serializedZco);
        break;

    default:
        /* bpSend() gave the serializedZco to a forwarder, so don't
         * zco_destroy(). */
        break;
    }

    if (signal.acsDue != 0)
    {
        destroyBpTimelineEvent(signal.acsDue);
    }

    signal.acsDue = 0;
    signal.serializedZco = 0;
    sdr_poke(acsSdr, signalAddr, signal);

    releaseSdrAcsSignal(signalLElt);

    if (sdr_end_xn(acsSdr) < 0)
    {
        ACSLOG_ERROR("Couldn't mark a serialized ACS as sent.");
        sdr_cancel_xn(bpSdr);
        return -1;
    }

    if(sdr_end_xn(bpSdr) < 0)
    {
        return -1;
    }

    return result > 0 ? 0 : -1;
}
예제 #4
0
파일: ltpcli.c 프로젝트: brnrc/ion-dtn
static void	*handleNotices(void *parm)
{
	/*	Main loop for LTP notice reception and handling.	*/

	Sdr			sdr = getIonsdr();
	ReceiverThreadParms	*rtp = (ReceiverThreadParms *) parm;
	char			*procName = "ltpcli";
	AcqWorkArea		*redWork;
	AcqWorkArea		*greenWork;
	LtpNoticeType		type;
	LtpSessionId		sessionId;
	unsigned char		reasonCode;
	unsigned char		endOfBlock;
	unsigned int		dataOffset;
	unsigned int		dataLength;
	Object			data;		/*	ZCO reference.	*/
	unsigned int		greenBuflen = 0;
	char			*greenBuffer = NULL;

	snooze(1);	/*	Let main thread become interruptable.	*/
	if (ltp_open(BpLtpClientId) < 0)
	{
		putErrmsg("ltpcli can't open client access.",
				itoa(BpLtpClientId));
		ionKillMainThread(procName);
		return NULL;
	}

	redWork = bpGetAcqArea(rtp->vduct);
	greenWork = bpGetAcqArea(rtp->vduct);
	if (redWork == NULL || greenWork == NULL)
	{
		ltp_close(BpLtpClientId);
		putErrmsg("ltpcli can't get acquisition work areas", NULL);
		ionKillMainThread(procName);
		return NULL;
	}

	/*	Can now start receiving notices.  On failure, take
	 *	down the CLI.						*/

	while (rtp->running)
	{
		if (ltp_get_notice(BpLtpClientId, &type, &sessionId,
				&reasonCode, &endOfBlock, &dataOffset,
				&dataLength, &data) < 0)
		{
			putErrmsg("Can't get LTP notice.", NULL);
			ionKillMainThread(procName);
			rtp->running = 0;
			continue;
		}

		switch (type)
		{
		case LtpExportSessionComplete:	/*	Xmit success.	*/
			if (data == 0)		/*	Ignore it.	*/
			{
				break;		/*	Out of switch.	*/
			}

			if (bpHandleXmitSuccess(data, 0) < 0)
			{
				putErrmsg("Crashed on xmit success.", NULL);
				ionKillMainThread(procName);
				rtp->running = 0;
				break;		/*	Out of switch.	*/
			}

			CHKNULL(sdr_begin_xn(sdr));
			zco_destroy(sdr, data);
			if (sdr_end_xn(sdr) < 0)
			{
				putErrmsg("Crashed on data cleanup.", NULL);
				ionKillMainThread(procName);
				rtp->running = 0;
			}

			break;		/*	Out of switch.		*/

		case LtpExportSessionCanceled:	/*	Xmit failure.	*/
			if (data == 0)		/*	Ignore it.	*/
			{
				break;		/*	Out of switch.	*/
			}

			if (bpHandleXmitFailure(data) < 0)
			{
				putErrmsg("Crashed on xmit failure.", NULL);
				ionKillMainThread(procName);
				rtp->running = 0;
				break;		/*	Out of switch.	*/
			}

			CHKNULL(sdr_begin_xn(sdr));
			zco_destroy(sdr, data);
			if (sdr_end_xn(sdr) < 0)
			{
				putErrmsg("Crashed on data cleanup.", NULL);
				ionKillMainThread(procName);
				rtp->running = 0;
			}

			break;		/*	Out of switch.		*/

		case LtpImportSessionCanceled:
			/*	None of the red data for the import
			 *	session (if any) have been received
			 *	yet, so nothing to discard.  In case
			 *	part or all of the import session was
			 *	green data, force deletion of retained
			 *	data.					*/

			sessionId.sourceEngineId = 0;
			sessionId.sessionNbr = 0;
			if (handleGreenSegment(greenWork, &sessionId,
					0, 0, 0, 0, NULL, NULL) < 0)
			{
				putErrmsg("Can't cancel green session.", NULL);
				ionKillMainThread(procName);
				rtp->running = 0;
			}

			break;		/*	Out of switch.		*/

		case LtpRecvRedPart:
			if (!endOfBlock)
			{
				/*	Block is partially red and
				 *	partially green.  Too risky
				 *	to wait for green EOB before
				 *	clearing the work area, so
				 *	just discard the data.		*/

				CHKNULL(sdr_begin_xn(sdr));
				zco_destroy(sdr, data);
				if (sdr_end_xn(sdr) < 0)
				{
					putErrmsg("Crashed: partially red.",
							NULL);
					ionKillMainThread(procName);
					rtp->running = 0;
				}

				break;		/*	Out of switch.	*/
			}

			if (acquireRedBundles(redWork, data,
					sessionId.sourceEngineId) < 0)
			{
				putErrmsg("Can't acquire bundle(s).", NULL);
				ionKillMainThread(procName);
				rtp->running = 0;
			}

			break;		/*	Out of switch.		*/

		case LtpRecvGreenSegment:
			if (handleGreenSegment(greenWork, &sessionId,
					endOfBlock, dataOffset, dataLength,
					data, &greenBuflen, &greenBuffer) < 0)
			{
				putErrmsg("Can't handle green segment.", NULL);
				ionKillMainThread(procName);
				rtp->running = 0;
			}

			/*	Discard the ZCO in any case.		*/

			CHKNULL(sdr_begin_xn(sdr));
			zco_destroy(sdr, data);
			if (sdr_end_xn(sdr) < 0)
			{
				putErrmsg("Crashed: green segment.", NULL);
				ionKillMainThread(procName);
				rtp->running = 0;
			}

			break;		/*	Out of switch.		*/

		default:
			break;		/*	Out of switch.		*/
		}

		/*	Make sure other tasks have a chance to run.	*/

		sm_TaskYield();
	}

	writeErrmsgMemos();
	writeMemo("[i] ltpcli receiver thread has ended.");

	/*	Free resources.						*/

	if (greenBuffer)
	{
		MRELEASE(greenBuffer);
	}

	bpReleaseAcqArea(greenWork);
	bpReleaseAcqArea(redWork);
	ltp_close(BpLtpClientId);
	return NULL;
}
예제 #5
0
파일: brsscla.c 프로젝트: brnrc/ion-dtn
static void	*sendBundles(void *parm)
{
	/*	Main loop for single bundle transmission thread
	 *	serving all BRS sockets.				*/

	SenderThreadParms	*parms = (SenderThreadParms *) parm;
	char			*procName = "brsscla";
	unsigned char		*buffer;
	Outduct			outduct;
	Sdr			sdr;
	Outflow			outflows[3];
	int			i;
	Object			bundleZco;
	BpExtendedCOS		extendedCOS;
	char			destDuctName[MAX_CL_DUCT_NAME_LEN + 1];
	unsigned int		bundleLength;
	int			ductNbr;
	int			bytesSent;
	Object			bundleAddr;
	Bundle			bundle;

	snooze(1);	/*	Let main thread become interruptable.	*/
	buffer = MTAKE(TCPCLA_BUFSZ);
	if (buffer == NULL)
	{
		putErrmsg("No memory for TCP buffer in brsscla.", NULL);
		ionKillMainThread(procName);
		return terminateSenderThread(parms);
	}

	sdr = getIonsdr();
	CHKNULL(sdr_begin_xn(sdr));
	sdr_read(sdr, (char *) &outduct, sdr_list_data(sdr,
			parms->vduct->outductElt), sizeof(Outduct));
	sdr_exit_xn(sdr);
	memset((char *) outflows, 0, sizeof outflows);
	outflows[0].outboundBundles = outduct.bulkQueue;
	outflows[1].outboundBundles = outduct.stdQueue;
	outflows[2].outboundBundles = outduct.urgentQueue;
	for (i = 0; i < 3; i++)
	{
		outflows[i].svcFactor = 1 << i;
	}

	/*	Can now begin transmitting to clients.			*/

	while (!(sm_SemEnded(parms->vduct->semaphore)))
	{
		if (bpDequeue(parms->vduct, outflows, &bundleZco,
				&extendedCOS, destDuctName, 0, -1) < 0)
		{
			break;
		}

		if (bundleZco == 0)		/*	Interrupted.	*/
		{
			continue;
		}

		CHKNULL(sdr_begin_xn(sdr));
		bundleLength = zco_length(sdr, bundleZco);
		sdr_exit_xn(sdr);
		ductNbr = atoi(destDuctName);
		if (ductNbr >= parms->baseDuctNbr
		&& ductNbr <= parms->lastDuctNbr
		&& parms->brsSockets[(i = ductNbr - parms->baseDuctNbr)] != -1)
		{
			bytesSent = sendBundleByTCP(NULL, parms->brsSockets + i,
					bundleLength, bundleZco, buffer);

			/*	Note that TCP I/O errors never block
			 *	the brsscla induct's output functions;
			 *	those functions never connect to remote
			 *	sockets and never behave like a TCP
			 *	outduct, so the _tcpOutductId table is
			 *	never populated.			*/

			if (bytesSent < 0)
			{
				putErrmsg("Can't send bundle.", NULL);
				break;
			}
		}
		else	/*	Can't send it; try again later?		*/
		{
			bytesSent = 0;
		}

		if (bytesSent < bundleLength)
		{
			/*	Couldn't send the bundle, so put it
			 *	in limbo so we can try again later
			 *	-- except that if bundle has already
			 *	been destroyed then just lose the ADU.	*/

			CHKNULL(sdr_begin_xn(sdr));
			if (retrieveSerializedBundle(bundleZco, &bundleAddr))
			{
				putErrmsg("Can't locate unsent bundle.", NULL);
				sdr_cancel_xn(sdr);
				break;
			}

			if (bundleAddr == 0)
			{
				/*	Bundle not found, so we can't
				 *	put it in limbo for another
				 *	attempt later; discard the ADU.	*/

				zco_destroy(sdr, bundleZco);
			}
			else
			{
				sdr_stage(sdr, (char *) &bundle, bundleAddr,
						sizeof(Bundle));
				if (bundle.extendedCOS.flags
						& BP_MINIMUM_LATENCY)
				{
					/*	We never put critical
					 *	bundles into limbo.	*/

					zco_destroy(sdr, bundleZco);
				}
				else
				{
					if (enqueueToLimbo(&bundle, bundleAddr)
							< 0)
					{
						putErrmsg("Can't save bundle.",
								NULL);
						sdr_cancel_xn(sdr);
						break;
					}
				}
			}

			if (sdr_end_xn(sdr) < 0)
			{
				putErrmsg("Failed handling brss xmit.", NULL);
				break;
			}
		}

		/*	Make sure other tasks have a chance to run.	*/

		sm_TaskYield();
	}

	ionKillMainThread(procName);
	writeMemo("[i] brsscla outduct has ended.");
	MRELEASE(buffer);
	return terminateSenderThread(parms);
}
예제 #6
0
파일: sdatest.c 프로젝트: brnrc/ion-dtn
static void	*sendItems(void *parm)
{
	SenderThreadParms	*stp = (SenderThreadParms *) parm;
	Sdr			sdr;
	char			buffer[MAX_LINE_LEN + 1];
	int			length;
	Object			extent;
	Object			item = 0;

	snooze(3);	/*	Let sda_run get started.		*/
	sdr = getIonsdr();
	while (stp->running)
	{
		if (fgets(buffer, MAX_LINE_LEN, stdin) == NULL)
		{
			sda_interrupt();
			stp->running = 0;
			continue;	/*	End of file, and test.	*/
		}

		length = istrlen(buffer, MAX_LINE_LEN) + 1;

		/*	Send NULL-terminated text line as an SDA item.	*/

		CHKNULL(sdr_begin_xn(sdr));
		extent = sdr_insert(sdr, buffer, length);
		if (extent)
		{
			item = ionCreateZco(ZcoSdrSource, extent, 0, length,
					0, 0, ZcoOutbound, NULL);
		}

		if (sdr_end_xn(sdr) < 0 || item == 0 || item == (Object) ERROR)
		{
			putErrmsg("Service data item insertion failed.", NULL);
			sda_interrupt();
			stp->running = 0;
			continue;
		}

		if (sda_send(stp->destEngineId, SDA_TEST_CLIENT, item) < 0)
		{
			putErrmsg("Service data item sending failed.", NULL);
			sda_interrupt();
			stp->running = 0;
			continue;
		}

		CHKNULL(sdr_begin_xn(sdr));
		zco_destroy(sdr, item);
		if (sdr_end_xn(sdr) < 0)
		{
			putErrmsg("Service data item deletion failed.", NULL);
			sda_interrupt();
			stp->running = 0;
			continue;
		}
	}

	writeErrmsgMemos();
	writeMemo("[i] sdatest sender thread has ended.");
	return NULL;
}
예제 #7
0
파일: bsspclo.c 프로젝트: brnrc/ion-dtn
int	bsspclo(int a1, int a2, int a3, int a4, int a5,
		int a6, int a7, int a8, int a9, int a10)
{
	char		*ductName = (char *) a1;
#else
int	main(int argc, char *argv[])
{
	char		*ductName = (argc > 1 ? argv[1] : NULL);
#endif
	Sdr		sdr;
	VOutduct	*vduct;
	PsmAddress	vductElt;
	vast		destEngineNbr;
	Outduct		outduct;
	ClProtocol	protocol;
	Outflow		outflows[3];
	int		i;
	int		running = 1;
	Object		bundleZco;
	BpExtendedCOS	extendedCOS;
	char		destDuctName[MAX_CL_DUCT_NAME_LEN + 1];
	BsspSessionId	sessionId;
	unsigned char	*buffer;
	Lyst		streams;
	Bundle		bundleImage;
	char		*dictionary = 0;
	unsigned int	bundleLength;

	if (ductName == NULL)
	{
		PUTS("Usage: bsspclo [-]<destination engine number>");
		return 0;
	}

	if (bpAttach() < 0)
	{
		putErrmsg("bsspclo can't attach to BP.", NULL);
		return -1;
	}

	sdr = getIonsdr();
	findOutduct("bssp", ductName, &vduct, &vductElt);
	if (vductElt == 0)
	{
		putErrmsg("No such bssp duct.", ductName);
		return -1;
	}

	if (vduct->cloPid != ERROR && vduct->cloPid != sm_TaskIdSelf())
	{
		putErrmsg("BSSPCLO task is already started for this duct.",
				itoa(vduct->cloPid));
		return -1;
	}

	/*	All command-line arguments are now validated.		*/

	buffer = (unsigned char *) MTAKE(BP_MAX_BLOCK_SIZE);
	if (buffer == NULL)
	{
		putErrmsg("Can't get buffer for decoding bundle ZCOs.", NULL);
		return -1;
	}

	streams = lyst_create_using(getIonMemoryMgr());
	if (streams == NULL)
	{
		putErrmsg("Can't create lyst of streams.", NULL);
		MRELEASE(buffer);
		return -1;
	}

	lyst_delete_set(streams, eraseStream, NULL);
	CHKERR(sdr_begin_xn(sdr));
	sdr_read(sdr, (char *) &outduct, sdr_list_data(sdr, vduct->outductElt),
			sizeof(Outduct));
	sdr_read(sdr, (char *) &protocol, outduct.protocol, sizeof(ClProtocol));
	sdr_exit_xn(sdr);
	destEngineNbr = strtovast(ductName);

	if (protocol.nominalRate == 0)
	{
		vduct->xmitThrottle.nominalRate = DEFAULT_BSSP_RATE;
	}
	else
	{
		vduct->xmitThrottle.nominalRate = protocol.nominalRate;
	}

	memset((char *) outflows, 0, sizeof outflows);
	outflows[0].outboundBundles = outduct.bulkQueue;
	outflows[1].outboundBundles = outduct.stdQueue;
	outflows[2].outboundBundles = outduct.urgentQueue;
	for (i = 0; i < 3; i++)
	{
		outflows[i].svcFactor = 1 << i;
	}

	if (bssp_attach() < 0)
	{
		putErrmsg("bsspclo can't initialize BSSP.", NULL);
		lyst_destroy(streams);
		MRELEASE(buffer);
		return -1;
	}

	/*	Set up signal handling.  SIGTERM is shutdown signal.	*/

	oK(bsspcloSemaphore(&(vduct->semaphore)));
	isignal(SIGTERM, shutDownClo);

	/*	Can now begin transmitting to remote duct.		*/

	writeMemo("[i] bsspclo is running.");
	while (running && !(sm_SemEnded(bsspcloSemaphore(NULL))))
	{
		if (bpDequeue(vduct, outflows, &bundleZco, &extendedCOS,
				destDuctName, 0, -1) < 0)
		{
			running = 0;	/*	Terminate CLO.		*/
			continue;
		}

		if (bundleZco == 0)	/*	Interrupted.		*/
		{
			continue;
		}

		if (decodeBundle(sdr, bundleZco, buffer, &bundleImage,
				&dictionary, &bundleLength) < 0)
		{
			putErrmsg("Can't decode bundle ZCO.", NULL);
			CHKERR(sdr_begin_xn(sdr));
			zco_destroy(sdr, bundleZco);
			if (sdr_end_xn(sdr) < 0)
			{
				putErrmsg("Failed destroying ZCO.", NULL);
				break;
			}

			continue;
		}

		switch (bssp_send(destEngineNbr, BpBsspClientId, bundleZco,
				isInOrder(streams, &bundleImage), &sessionId))
		{
		case 0:
			putErrmsg("Unable to send this bundle via BSSP.", NULL);
			break;

		case -1:
			putErrmsg("BsspSend failed.", NULL);
			running = 0;	/*	Terminate CLO.		*/
		}

		/*	Make sure other tasks have a chance to run.	*/

		sm_TaskYield();

		/*	Note: bundleZco is destroyed later, when BSSP's
		 *	ExportSession is closed following transmission
		 *	of bundle ZCOs as aggregated into a block.	*/
	}

	writeErrmsgMemos();
	writeMemo("[i] bsspclo duct has ended.");
	lyst_destroy(streams);
	MRELEASE(buffer);
	ionDetach();
	return 0;
}