/// GSourceFunc function to invoke discover member function at the timed interval.
/// This function is called by the g_main_loop mechanism when the rediscover timeout elapses.
FSTATIC gboolean
_discovery_rediscover(gpointer vself)	///<[in/out] Object to perform discovery on
{
	Discovery*	self = CASTTOCLASS(Discovery, vself);

	return self->discover(self);
}
/// Function for finalizing
FSTATIC void
_packetdecoder_finalize(AssimObj* selfobj)
{
	PacketDecoder* self = CASTTOCLASS(PacketDecoder, selfobj);

	FREE(self->_frametypemap);	self->_frametypemap = NULL;
	self->_pfinalize(selfobj);
}
/// Return the non-const private key with the given id
WINEXPORT CryptFramePrivateKey*
cryptframe_private_key_by_id(const char* key_id) ///<[in] Key id of the given private key being sought
{
	gpointer	ret;
	INITMAPS;
	ret = (key_id ? g_hash_table_lookup(private_key_map, key_id): NULL);
	return (ret ? CASTTOCLASS(CryptFramePrivateKey, ret): NULL);
}
/// Return the non-const public key with the given id
WINEXPORT CryptFramePublicKey*
cryptframe_public_key_by_id(const char* key_id)	///[in] Key id of public key being sought
{
	gpointer	ret;
	INITMAPS;
	ret = (key_id ? g_hash_table_lookup(public_key_map, key_id): NULL);
	return (ret ? CASTTOCLASS(CryptFramePublicKey, ret): NULL);
}
Beispiel #5
0
/// Examine our queues and run anything that needs running.
/// (this code is more expensive than it could be, but in practice it may not matter)
FSTATIC gboolean
_resource_queue_runqueue(gpointer pself)
{
	ResourceQueue* self = CASTTOCLASS(ResourceQueue, pself);
	GHashTableIter	iter;
	gpointer	key;
	gpointer	value;
	gint64		now = g_get_monotonic_time();
	gboolean	anyelems = FALSE;

	DEBUGMSG3("%s.%d: Examining queues", __FUNCTION__, __LINE__);
	g_hash_table_iter_init(&iter, self->resources);

	while(g_hash_table_iter_next(&iter, &key, &value)) {
		GQueue*	rsc_q = (GQueue*)value;
		GList*	qelem;
		gboolean	any_running = FALSE;
		for (qelem=rsc_q->head; NULL != qelem; qelem=qelem->next) {
			RscQElem*	qe = CASTTOCLASS(RscQElem, qelem->data);
			anyelems = TRUE;
			if (qe->cmd->is_running) {
				any_running = TRUE;
				break;
			}
		}
		if (any_running) {
			continue;
		}
		DEBUGMSG4("%s.%d: No resource jobs are running.", __FUNCTION__, __LINE__);
		for (qelem=rsc_q->head; NULL != qelem; qelem=qelem->next) {
			RscQElem*	qe = CASTTOCLASS(RscQElem, qelem->data);
			if (now >= qe->cmd->starttime) {
				REF(self);	// We undo this when process exits
				self->activechildcnt += 1;
				qe->cmd->execute(qe->cmd);
				break;
			}
		}
	}
	if (!anyelems && self->timerid >= 0) {
		g_source_remove(self->timerid);
		self->timerid = -1;
	}
	return anyelems;
}
/// Return the key_id associated with the given destination address
WINEXPORT const char *
cryptframe_get_dest_key_id(const NetAddr* destaddr)
{
	gpointer		g_receiver_key;
	CryptFramePublicKey*	receiver_key;
	INITMAPS;
	g_receiver_key = g_hash_table_lookup(addr_to_public_key_map, destaddr);
	if (NULL == g_receiver_key) {
		return NULL;
	}
	receiver_key = CASTTOCLASS(CryptFramePublicKey, g_receiver_key);
	return receiver_key->key_id;
}
Beispiel #7
0
/// Finalize (free) a CryptCurve25519 object
FSTATIC void
_cryptcurve25519_finalize(AssimObj* aself)	///< Object to finalize/free
{
	CryptCurve25519*	self = CASTTOCLASS(CryptCurve25519, aself);
	
	if (self->public_key) {
		UNREF(self->public_key);
	}
	if (self->private_key) {
		UNREF(self->private_key);
	}
	_parentclass_finalize(aself);
}
/// Finalize (destructor) function for our CryptFramePublicKey objects
FSTATIC void
_cryptframe_finalize(AssimObj* aself) ///< object to finalize/destroy
{
	CryptFrame*	self = CASTTOCLASS(CryptFrame, aself);
	if (self->sender_key_id) {
		g_free(self->sender_key_id);
		self->sender_key_id = NULL;
	}
	if (self->receiver_key_id) {
		g_free(self->receiver_key_id);
		self->receiver_key_id = NULL;
	}
	_parentclass_finalize(aself);
}
Beispiel #9
0
/// Function for destroying data when an element is removed from self->resources hash table
FSTATIC void
_resource_queue_hash_data_destructor(gpointer dataptr)
{
	GQueue* 	q = (GQueue*) dataptr;
	GList*		l;

	for (l=q->head; NULL != l; l=l->next) {
		RscQElem*	qelem = CASTTOCLASS(RscQElem, l->data);
		_resource_queue_qelem_finalize(qelem); l->data = NULL; qelem = NULL;
	}
	g_queue_clear(q);
	q = NULL;
	dataptr = NULL;
}
/// Finalize (destructor) function for our CryptFramePublicKey objects
FSTATIC void
_cryptframe_publickey_finalize(AssimObj* pubkey) ///< object to finalize/destroy
{
	CryptFramePublicKey*	self = CASTTOCLASS(CryptFramePublicKey, pubkey);
	if (self->key_id) {
		g_free(self->key_id);
		self->key_id = NULL;
	}
	if (self->public_key) {
		g_free(self->public_key);
		self->public_key = NULL;
	}
	_assimobj_finalize(pubkey);
}
/// Finalize (destructor) function for our CryptFramePrivateKey objects
FSTATIC void
_cryptframe_privatekey_finalize(AssimObj* privkey) ///< object to finalize/destroy
{
	CryptFramePrivateKey*	self = CASTTOCLASS(CryptFramePrivateKey, privkey);
	if (self->key_id) {
		g_free(self->key_id);
		self->key_id = NULL;
	}
	if (self->private_key) {
		g_free(self->private_key);
		self->private_key = NULL;
	}
	_assimobj_finalize(privkey);
}
/// Called every second during tests
gboolean
timeout_agent(gpointer ignored)
{
	ReliableUDP*	io = CASTTOCLASS(ReliableUDP, nettransport);

	(void)ignored;
	if (nano_hbstats.heartbeat_count > (unsigned)maxpkts) {
		g_message("QUITTING NOW! (heartbeat count)");
		io->_protocol->closeall(io->_protocol);
		nano_initiate_shutdown();
		return FALSE;
	}
	return TRUE;
}
/// Routine to (fake) validate that we have proper authentication...
FSTATIC gboolean
test_cma_authentication(const FrameSet*fs, NetAddr* fromaddr)
{
	gpointer	maybecrypt = g_slist_nth_data(fs->framelist, 1);
	Frame*		mightbecrypt;
	(void)fromaddr;
	/// For our purposes, we don't much care how it's encrypted...
	g_return_val_if_fail(maybecrypt != NULL, FALSE);
	mightbecrypt = CASTTOCLASS(Frame, maybecrypt);
	if (mightbecrypt->type != FRAMETYPE_CRYPTCURVE25519) {
		DUMP("test_cma_authentication: ", &fs->baseclass, " was BAD");
		return FALSE;
	}
	return TRUE;
}
/// Function to perform setup for child between fork and exec (for UNIX-like systems)
/// It doesn't get called under Windows.
FSTATIC void
_childprocess_setup_child(gpointer childprocess_object)
{
#ifdef WIN32
	(void)childprocess_object;
#else
	ChildProcess*	self = CASTTOCLASS(ChildProcess, childprocess_object);
#	ifdef HAVE_SETPGID
	setpgid(0,0);
#	else
	setpgrp(0, 0);
#	endif
	(void)self;
#endif
}
///
///	Set the encryption key to use when sending to destaddr
///	Set destkey to NULL to stop encrypting to that destination
WINEXPORT gboolean
cryptframe_set_dest_key_id(NetAddr*destaddr,	///< Destination addr,port
			   const char * key_id)		///< Public key id to use when encrypting
{
	CryptFramePublicKey*	destkey;
	INITMAPS;
	g_return_val_if_fail(NULL != destaddr && NULL != key_id, FALSE);
	destkey = g_hash_table_lookup(public_key_map, key_id);
	if (NULL == destkey) {
		g_critical("%s.%d: No key associated with key id %s"
		,	__FUNCTION__, __LINE__, key_id);
		return FALSE;
	}
	cryptframe_set_dest_public_key(destaddr, CASTTOCLASS(CryptFramePublicKey, destkey));
	return TRUE;
}
/// Update packet data from the frame
FSTATIC void
_seqnoframe_updatedata(Frame* fself,		///< object whose data will be put into FrameSet packet
                       gpointer tlvptr,		///< pointer to our current TLV entry
                       gconstpointer pktend,	///< end of packet
                       FrameSet* fs)		///< FrameSet that we're doing this for
{
    SeqnoFrame* self = CASTTOCLASS(SeqnoFrame, fself);
    // NOTE - this gets rid of the "const" coming out of get_generic_tlv_value...
    ///@todo add a new get_generic_nonconst_tlv_value() function.
    guint8* pktpos = get_generic_tlv_nonconst_value(tlvptr, pktend);
    (void)fs;
    g_return_if_fail(NULL != pktpos);

    tlv_set_guint32(pktpos,                                self->_sessionid, pktend);
    tlv_set_guint64(pktpos+sizeof(guint32),                self->_reqid, pktend);
    tlv_set_guint16(pktpos+sizeof(guint32)+sizeof(guint64),self->_qid, pktend);
}
Beispiel #17
0
/// Cancel all outstanding requests
FSTATIC void
_resource_queue_cancelall(ResourceQueue* self)
{
	GHashTableIter	iter;
	gpointer	pkey;
	gpointer	pvalue;

	g_hash_table_iter_init(&iter, self->resources);
	while(g_hash_table_iter_next(&iter, &pkey, &pvalue)) {
		GQueue*	q = (GQueue*) pvalue;
		GList*	l;
		for (l=q->head; NULL != l; l=l->next) {
			RscQElem*	qe = CASTTOCLASS(RscQElem, l->data);
			_resource_queue_cancel(self, qe->cmd->request);
		}
	}
}
Beispiel #18
0
/// Finalize a ResourceQueue -- RIP
FSTATIC void
_resource_queue_finalize(AssimObj* aself)
{
	ResourceQueue*	self = CASTTOCLASS(ResourceQueue, aself);

	self->shuttingdown = TRUE;
	if (self->activechildcnt > 0) {
		return;
	}
	if (self->resources) {
		g_hash_table_destroy(self->resources);
		self->resources = NULL;
	}
	if (self->timerid >= 0) {
		g_source_remove(self->timerid);
		self->timerid = -1;
	}
	_assimobj_finalize(&self->baseclass);
	self = NULL;
}
/// Initialize our frame type map.
/// Post-condition:  Every element of 'frametypemap' is initialized with a valid function pointer.
PacketDecoder*
packetdecoder_new(guint objsize, const FrameTypeToFrame* framemap, gint mapsize)
{
	gint		j;
	AssimObj*	baseobj;
	PacketDecoder*	self;
	
	if (objsize < sizeof(PacketDecoder)) {
		objsize = sizeof(PacketDecoder);
	}
	if (NULL == framemap) {
		framemap = _defaultmap;
		mapsize = DIMOF(_defaultmap);
	}

	baseobj = assimobj_new(objsize);
	proj_class_register_subclassed(baseobj, "PacketDecoder");
	self = CASTTOCLASS(PacketDecoder, baseobj);
	

	self->_pfinalize = baseobj->_finalize;
	baseobj->_finalize = _packetdecoder_finalize;
	self->pktdata_to_framesetlist = _pktdata_to_framesetlist;
	self->_maxframetype = 0;
	self->_framemap = framemap;
	self->_framemaplen = mapsize;

	for (j=0; j < self->_framemaplen; ++j) {
		if (self->_framemap[j].frametype > self->_maxframetype) {
			self->_maxframetype = self->_framemap[j].frametype;
		}
	}
	self->_frametypemap = MALLOC0((self->_maxframetype+1)*sizeof(gpointer));
	for (j=0; j <= self->_maxframetype; ++j) {
		self->_frametypemap[j] = unknownframe_tlvconstructor;
	}
	for (j=0; j < self->_framemaplen; ++j) {
		self->_frametypemap[self->_framemap[j].frametype] = self->_framemap[j].constructor;
	}
	return self;
}
/// Construct Frame (SeqnoFrame) object from marshalled packet data
Frame*
seqnoframe_tlvconstructor(gpointer tlvstart,		///<[in] Start of SeqnoFrame TLV area
                          gconstpointer pktend,		///<[in] first byte past end of packet
                          gpointer* ignorednewpkt,	///<[ignored] replacement packet
                          gpointer* ignoredpktend)	///<[ignored] end of replacement packet
{
    SeqnoFrame*	ret;
    guint16		length  = get_generic_tlv_len(tlvstart, pktend);
    guint16		tlvtype = get_generic_tlv_type(tlvstart, pktend);
    const guint8* valpos = get_generic_tlv_value(tlvstart, pktend);

    (void)ignorednewpkt;
    (void)ignoredpktend;
    g_return_val_if_fail(length == (sizeof(guint64)+sizeof(guint16)+sizeof(guint32)), NULL);

    ret = seqnoframe_new(tlvtype, 0);
    ret->_sessionid = tlv_get_guint32(valpos, pktend);
    ret->setreqid(ret, tlv_get_guint64(valpos+sizeof(guint32), pktend));
    ret->setqid(ret, tlv_get_guint16(valpos+sizeof(guint32)+sizeof(guint64), pktend));
    return CASTTOCLASS(Frame, ret);
}
/// Function to handle child timeouts.
/// It implements a very simple, linear state machine...
FSTATIC gboolean
_childprocess_timeout(gpointer childprocess_object)
{
	ChildProcess*	self;
	DEBUGMSG("%s:%d Called from timeout for process with user_data = %p"
	,	__FUNCTION__, __LINE__, childprocess_object);
	self = CASTTOCLASS(ChildProcess, childprocess_object);
	if ((unsigned)(self->child_state) < DIMOF(signalmap)) {
#ifdef WIN32
		TerminateProcess(self->child_pid, -1);
#else
		(void)kill(self->child_pid, signalmap[self->child_state].signal);
#endif
		self->timeoutsrc_id = g_timeout_add_seconds
		(	signalmap[self->child_state].next_timeout
		,	_childprocess_timeout, self);
		self->child_state += 1;
	}else{
		_childprocess_childexit(self->child_pid, 0xffffffff, self);
	}
	return FALSE;
}
/// Finalizing function for Discovery objects
FSTATIC void
_discovery_finalize(AssimObj* gself)	///<[in/out] Object to finalize (free)
{
	Discovery*	self = CASTTOCLASS(Discovery, gself);
	char *		instancename = self->_instancename;
	
	if (self->_timerid > 0) {
		g_source_remove(self->_timerid);
		self->_timerid = 0;
	}
	if (self->_config) {
		UNREF(self->_config);
	}
	if (_discovery_timers && instancename) {
		self->_instancename = NULL;	// Avoid infinite recursion...
		g_hash_table_remove(_discovery_timers, instancename);
	}
	if (instancename) {
		g_free(instancename);
		self->_instancename = instancename = NULL;
	}
	
	FREECLASSOBJ(self); self=NULL;
}
/// Routine to pretend to be the initial CMA
void
fakecma_startup(AuthListener* auth, FrameSet* ifs, NetAddr* nanoaddr)
{
	FrameSet*	pkt;
	NetGSource*	netpkt = auth->baseclass.transport;
	char *		nanostr = nanoaddr->baseclass.toString(nanoaddr);
	GSList*		thisgsf;
	const char *	keyid = NULL;

	(void)ifs;
	g_message("CMA received startup message from nanoprobe at address %s/%d."
	,	nanostr, nanoaddr->port(nanoaddr));
	g_free(nanostr); nanostr = NULL;
	check_JSON(ifs);

	netpkt->_netio->addalias(netpkt->_netio, nanoaddr, destaddr);
	
	// Set up our crypto...
	cryptframe_set_dest_key_id(nanoaddr, cryptframe_get_signing_key_id());
	cryptframe_set_dest_key_id(destaddr, cryptframe_get_signing_key_id());
	cryptframe_associate_identity(CMA_IDENTITY_NAME, cryptframe_get_signing_key_id());
	cryptframe_set_encryption_method(cryptcurve25519_new_generic);
	for (thisgsf = ifs->framelist; thisgsf; thisgsf=thisgsf->next) {
		Frame*	thisframe = CASTTOCLASS(Frame, thisgsf->data);
		if (thisframe->type == FRAMETYPE_KEYID) {
			CstringFrame* csf = CASTTOCLASS(CstringFrame, thisframe);
			keyid = (const char *)csf->baseclass.value;
		}else if (keyid && thisframe->type == FRAMETYPE_PUBKEYCURVE25519) {
			cryptcurve25519_save_public_key(keyid, thisframe->value
			,	thisframe->length);
		}
	}

	// Send the configuration data to our new "client"
	pkt = create_setconfig(nanoconfig);
	netpkt->_netio->sendareliablefs(netpkt->_netio, nanoaddr, DEFAULT_FSP_QID, pkt);
	UNREF(pkt);

	// Now tell them to send/expect heartbeats to various places
	pkt = create_sendexpecthb(auth->baseclass.config, FRAMESETTYPE_SENDEXPECTHB, destaddr, 1);
	netpkt->_netio->sendareliablefs(netpkt->_netio, nanoaddr, DEFAULT_FSP_QID, pkt);
	UNREF(pkt);

	{
		const char *	monopjson[] = { START, MONITOR, STOP};
		unsigned	j;
		// Create a frameset for a few resource operations
		pkt = frameset_new(FRAMESETTYPE_DORSCOP);
		for (j=0; j < DIMOF(monopjson); j++) {
			CstringFrame*	csf = cstringframe_new(FRAMETYPE_RSCJSON,0);
			csf->baseclass.setvalue(&csf->baseclass, g_strdup(monopjson[j])
			,	strlen(monopjson[j])+1, g_free);
			frameset_append_frame(pkt, &csf->baseclass);
			UNREF2(csf);
		}
		netpkt->_netio->sendareliablefs(netpkt->_netio, nanoaddr, DEFAULT_FSP_QID, pkt);
		UNREF(pkt);
	}
	{
		const char *	discoverjson[] = {SWITCHDISCOVER, ARPDISCOVER};
		const char *	discoverinstnm[] = {SWINSTNM, ARPINSTNM};
		unsigned	j;
		// Create a frameset for a few discovery operations
		pkt = frameset_new(FRAMESETTYPE_DODISCOVER);
		for (j=0; j < DIMOF(discoverjson); j++) {
			CstringFrame*	csf = cstringframe_new(FRAMETYPE_DISCJSON,0);
			CstringFrame*	inst = cstringframe_new(FRAMETYPE_DISCNAME,0);
			fprintf(stderr, "Creating discovery frameset: %s: %s\n"
			,	discoverinstnm[j]
			,	discoverjson[j]);
			inst->baseclass.setvalue(&inst->baseclass, g_strdup(discoverjson[j])
			,	strlen(discoverjson[j])+1, g_free);
			frameset_append_frame(pkt, &inst->baseclass);
			UNREF2(inst);
			csf->baseclass.setvalue(&csf->baseclass, g_strdup(discoverjson[j])
			,	strlen(discoverjson[j])+1, g_free);
			frameset_append_frame(pkt, &csf->baseclass);
			UNREF2(csf);
		}
		netpkt->_netio->sendareliablefs(netpkt->_netio, nanoaddr, DEFAULT_FSP_QID, pkt);
		UNREF(pkt);
	}
	pkt = frameset_new(FRAMESETTYPE_ACKSTARTUP);
	netpkt->_netio->sendareliablefs(netpkt->_netio, nanoaddr, DEFAULT_FSP_QID, pkt);
	UNREF(pkt);
	g_info("ACKSTARTUP packet queued to send");
}
Beispiel #24
0
/// Called when an operation completes - it calls requestor's callback if no repeat,
/// and requeues it if it is going to repeat
FSTATIC void
_resource_queue_endnotify
(	ConfigContext*	request
,	gpointer	user_data
,	enum HowDied	exittype
,	int		rc
,	int		signal
,	gboolean	core_dumped
,	const char*	stringresult)
{
	RscQElem*	self = CASTTOCLASS(RscQElem, user_data);
	ResourceQueue*	parent = self->parent;
	ResourceCmd*	cmd = self->cmd;
	gboolean	shouldrepeat = TRUE;


	g_queue_remove(self->ourQ, self);
	parent->activechildcnt -= 1;
	if (parent->shuttingdown && parent->activechildcnt <= 0) {
		_resource_queue_finalize(&parent->baseclass);
		return;
	}
	DEBUGMSG1("%s.%d: EXIT happened exittype:%d repeat:%d, cancelme:%d", __FUNCTION__, __LINE__
	,	exittype,  self->repeatinterval, self->cancelme);

	// Should this request repeat?
	if (self->cancelme || (self->cancelonfail && exittype != EXITED_ZERO)
	||	(0 == self->repeatinterval)) {
		shouldrepeat = FALSE;
	}else{
		shouldrepeat = TRUE;
	}
	// Notify the user that their repeating command flipped status, or has stopped running
	// (i.e., if it was failing but now works, or was working, but now fails)
	if (FALSE == shouldrepeat
	||	(exittype == EXITED_ZERO && !cmd->last_success)
	||	(exittype != EXITED_ZERO && cmd->last_success)) {
		DEBUGMSG1("%s.%d: Calling callback for request id " FMT_64BIT "d."
		,	__FUNCTION__, __LINE__, self->requestid);
		self->callback(request, self->user_data, exittype, rc, signal, core_dumped
		,		stringresult);
		if (shouldrepeat && exittype == EXITED_ZERO && stringresult) {
			g_message("%s: %s", cmd->loggingname, stringresult);
		}
	}
	cmd->last_success = (exittype == EXITED_ZERO);

	if (shouldrepeat) {
		DEBUGMSG1("%s.%d: Repeat request id " FMT_64BIT "d.", __FUNCTION__, __LINE__
		,	self->requestid);
		self->queuetime = g_get_monotonic_time();
		cmd->starttime = self->queuetime + (self->repeatinterval*uSPERSEC);
		g_queue_push_tail(self->ourQ, self);
		_resource_queue_runqueue(self->parent);
	}else{
		DEBUGMSG1("%s.%d: Don't repeat request id " FMT_64BIT "d.", __FUNCTION__, __LINE__
		,	self->requestid);
		if (g_queue_get_length(self->ourQ) == 0) {
			g_hash_table_remove(self->parent->resources, cmd->resourcename);
		}
		_resource_queue_runqueue(self->parent);
		_resource_queue_qelem_finalize(self);
		self = NULL;
	}
	// Undo the ref we did before starting this job
	UNREF(parent);
}
Beispiel #25
0
///
/// We update the data in the packet from our CryptCurve25519 object with the
/// side-effect of encrypting all the frames already put into the packet.  Note that
/// this only works because we always construct the packet from the end back to the
/// beginning.  We do this in-place - fortunately the algorithms allow that...
/// We effectively suck all the remaining frames into a single encrypted frame...
FSTATIC void	
_cryptcurve25519_updatedata(Frame* f,			///< Frame to marshall
			    gpointer tlvstart,		///< Start of our Frame in the packet
			    gconstpointer pktend,	///< Last byte in the allocated packet
			    FrameSet* unused_fs)	///< Pointer to our containing frameset
{
	CryptCurve25519*self		= CASTTOCLASS(CryptCurve25519, f);
	const guint8*	pktend8		= pktend;
	//guint8*	tlvstart8	= tlvstart;
	guint8*		tlvval;
	guint8*		valptr;
	guint32		plaintextoffset;
	guint32		plaintextsize;
	guint32		cyphertextoffset;
	guint32		nonceoffset;
	guint32		tlvsize;
	unsigned char*	nonce;
	int		j;

	(void)unused_fs;
	
	// [key1, key2, nonce, MAC, plaintext]

	DUMP3(__FUNCTION__, &f->baseclass, " is CryptCurve25519 Frame being processed.");
	DEBUGMSG3("%s.%d: tlvstart:%p, pktend:%p", __FUNCTION__, __LINE__, tlvstart, pktend);
	// The plain text starts immediately after our (incoming) frame
	plaintextoffset = f->length;					// Plain text starts here
	cyphertextoffset = plaintextoffset - crypto_box_MACBYTES;	// Preceded by MAC
	nonceoffset = cyphertextoffset - crypto_box_NONCEBYTES;		// Preceded by nonce
	// Our (outgoing) frame consists of the original incoming frame plus all other frames after ours
	tlvval = get_generic_tlv_nonconst_value(tlvstart, pktend);
	tlvsize = pktend8 - tlvval;
	plaintextsize = (tlvsize - plaintextoffset);

	// Generate a "nonce" as part of the packet - make known plaintext attacks harder
	// ... lots of our plaintext is easy to figure out ...
	nonce = tlvval + nonceoffset;
	DEBUGMSG3("%s.%d: generating random nonce (%p, %d, %p)", __FUNCTION__, __LINE__
	,	nonce, (int)crypto_box_NONCEBYTES, nonce+crypto_box_NONCEBYTES);
	randombytes_buf(nonce, crypto_box_NONCEBYTES);
	DEBUGMSG3("%s.%d: random nonce generated.", __FUNCTION__, __LINE__);

	DEBUGMSG3("%s.%d: public->key_id: [%s], private_key->key_id: [%s]", __FUNCTION__, __LINE__
	,	self->public_key->key_id, self->private_key->key_id);
	DEBUGMSG3("%s.%d: calling crypto_box_easy(%p,%p,%d,%p,%p,%p)", __FUNCTION__, __LINE__
	,	tlvval+cyphertextoffset, tlvval+plaintextoffset, plaintextsize
	,	nonce, self->public_key->public_key, self->private_key->private_key);
	DEBUGCKSUM4("plain text cksum:", tlvval+plaintextoffset, plaintextsize);
	DEBUGCKSUM4("receiver public key cksum:",self->public_key -> public_key, crypto_box_PUBLICKEYBYTES);
	DEBUGCKSUM4("sender  private key cksum:", self->private_key->private_key, crypto_box_SECRETKEYBYTES);
	DEBUGCKSUM4("nonce cksum:", nonce, crypto_box_NONCEBYTES);
	// Encrypt in-place [we previously allocated enough space for authentication info]
	crypto_box_easy(tlvval+cyphertextoffset, tlvval+plaintextoffset, plaintextsize
	,	nonce, self->public_key->public_key, self->private_key->private_key);
	DEBUGMSG4("cypher offset versus tlvstart: %ld", (long)(tlvval+cyphertextoffset-(guint8*)tlvstart));
	DEBUGCKSUM4("cypher text checksum:", tlvval+cyphertextoffset, plaintextsize+crypto_box_MACBYTES);
	set_generic_tlv_type(tlvstart, self->baseclass.baseclass.type, pktend);
	set_generic_tlv_len(tlvstart, tlvsize, pktend);
	// Put in the frame type, length, key name length, and key name for both keys
	// We're the sender - our [private] key name goes first, then the receiver's [public] key name
	valptr = get_generic_tlv_nonconst_value(tlvstart, pktend);
	for (j=0; j < 2; ++j) {
		char *	key_id = (j == 0 ? self->baseclass.sender_key_id : self->baseclass.receiver_key_id);
		int	keylen = strlen(key_id)+1;
		tlv_set_guint8(valptr, keylen, pktend);
		valptr += 1;
		g_strlcpy((char *)valptr, key_id, keylen);
		valptr += keylen;
	}
	DEBUGMSG3("%s.%d: returning after next assert (tlvval:%p, tlvsize%d, pktend:%p"
	,	__FUNCTION__, __LINE__, tlvval, (int)tlvsize, pktend);
	g_assert((tlvval + tlvsize) == pktend);
	DEBUGMSG3("%s.%d: returning (assert passed).", __FUNCTION__, __LINE__);
}
/// Function called when the child (finally) exits...
FSTATIC void
_childprocess_childexit(GPid pid, gint status, gpointer childprocess_object)
{
	ChildProcess*	self = CASTTOCLASS(ChildProcess, childprocess_object);
	gboolean	signalled = WIFSIGNALED(status);
	int		exitrc = 0;
	int		signal = 0;
	gboolean	logexit = FALSE;
	enum HowDied	howwedied = NOT_EXITED;
	(void)pid;

	if (self->timeoutsrc_id > 0)  {
		g_source_remove(self->timeoutsrc_id);
		DEBUGMSG3("%s.%d: Removed timeout %d for process with user_data = %p"
		,	__FUNCTION__, __LINE__, self->timeoutsrc_id, self);
		self->timeoutsrc_id = 0;
	}
	// If it refused to die, then the status is invalid
	if ((guint)(self->child_state) >= DIMOF(signalmap)) {
		howwedied = EXITED_HUNG;
	}else if ((guint)self->child_state != CHILDSTATE_RUNNING) {
 		// Then we tried to kill it...
		howwedied = EXITED_TIMEOUT;
		signal = signalled ? WTERMSIG(status) : 0;
	}else{
		if (signalled) {
			signal = WTERMSIG(status);
			howwedied = EXITED_SIGNAL;
		}else{
			exitrc = WEXITSTATUS(status);
			howwedied = (exitrc == 0 ? EXITED_ZERO : EXITED_NONZERO);
		}
	}
	switch (howwedied) {
		case EXITED_SIGNAL:	/*FALLTHROUGH*/
		case EXITED_TIMEOUT:	/*FALLTHROUGH*/
		case EXITED_HUNG:
			logexit = self->logmode  > CHILD_NOLOG;
			break;
		case EXITED_NONZERO:
			logexit = self->logmode  >= CHILD_LOGERRS;
			break;
		case EXITED_ZERO:
			logexit = self->logmode  >= CHILD_LOGALL;
			break;
		default:
			// We'll never produce any other values above
			/*NOTREACHED*/
			logexit = TRUE;
			break;
	}
	if (logexit) {
		switch (howwedied) {
		case EXITED_SIGNAL:
			g_warning("Child process [%s] died from signal %d%s."
			,	self->loggingname, signal, WCOREDUMP(status) ? " (core dumped)" : "");
			break;
		case EXITED_TIMEOUT:
			if (signalled) {
				g_warning("Child process [%s] timed out after %d seconds [signal %d%s]."
				,	self->loggingname, self->timeout, signal
				,	WCOREDUMP(status) ? " (core dumped)" : "");
			}else{
				g_warning("Child process [%s] timed out after %d seconds."
				,	self->loggingname, self->timeout);
			}
			break;
		case EXITED_HUNG:
			g_warning("Child process [%s] timed out after %d seconds and could not be killed."
			,	self->loggingname, self->timeout);
			break;
		case EXITED_NONZERO:
			g_message("Child process [%s] exited with return code %d."
			,	self->loggingname, exitrc);
			break;
		case EXITED_ZERO:
			g_message("Child process [%s] exited normally.", self->loggingname);
			break;
		default:/*NOTREACHED*/
			break;
		}
	}

	DEBUGMSG2("%s.%d: Exit happened howwedied:%d", __FUNCTION__, __LINE__
	,	howwedied);
	if (!self->stdout_src->atEOF) {
		//DEBUGMSG3("Child %d [%s] EXITED but output is not at EOF [fd%d]", pid
		//,	self->loggingname, self->stdout_src->gfd.fd);
		self->stdout_src->readmore(self->stdout_src);
	}
	if (!self->stderr_src->baseclass.atEOF) {
		self->stderr_src->baseclass.readmore(&self->stderr_src->baseclass);
	}
	self->notify(self, howwedied, exitrc, signal, WCOREDUMP(status));
	self->child_state = -1;
	DEBUGMSG5("%s.%d: UNREF child: %p", __FUNCTION__,__LINE__, self);
	UNREF(self);	// Undo the REF(self) in our constructor
}