Exemplo n.º 1
0
void PDCHL1Downlink::mchResync()
{
	// If the encoder's clock is far from the current BTS clock,
	// get it caught up to something reasonable.
	Time now = gBTS.time();
	int32_t delta = mchNextWriteTime-now;
	GPRSLOG(8) << "PDCHL1Downlink" <<LOGVAR(mchNextWriteTime)
			<<LOGVAR(now)<< LOGVAR(delta);
	if ((delta<0) || (delta>(51*26))) {
		mchNextWriteTime = now;
		//mchNextWriteTime.TN(now.TN());	// unneeded?
		mchNextWriteTime.rollForward(mchMapping.frameMapping(mchTotalBursts),mchMapping.repeatLength());
		GPRSLOG(2) <<"PDCHL1Downlink RESYNC" << LOGVAR(mchNextWriteTime) << LOGVAR(now);
	}
}
Exemplo n.º 2
0
void PDCHL1Downlink::bugFixIdleFrame()
{
	// DEBUG: We are only using this function to fix this problem for now.
	if (gFixIdleFrame) {
		// For this debug purpose, the mssage is sent on the next frame
		// TODO: debug purpose only! This only works for one channel!
		//Time tnext(gBSNNext.FN());
		//gBTS.clock().wait(tnext);
	}

	// Did we make it in time?
	{
	Time tnow = gBTS.time();
	int fn = tnow.FN();
	int mfn = (fn / 13);			// how many 13-multiframes
	int rem = (fn - (mfn*13));	// how many blocks within the last multiframe.
	int tbsn = mfn * 3 + ((rem==12) ? 2 : (rem/4));
	GPRSLOG(2) <<"idleframe"<<LOGVAR(fn)<<LOGVAR(tbsn)<<LOGVAR(rem);
	}

	/***
	if (mchIdleFrame.size() == 0) {
		RLCMsgPacketDownlinkDummyControlBlock *dummymsg = new RLCMsgPacketDownlinkDummyControlBlock();
		mchIdleFrame.set(BitVector(RLCBlockSizeInBits[ChannelCodingCS1]));
		dummymsg->write(mchIdleFrame);
		delete dummymsg;
	}
	send1Frame(mchIdleFrame,ChannelCodingCS1,true);
	***/
}
Exemplo n.º 3
0
void BSSGWriteLowSide(NSMsg *ulmsg)
{
	if (gBSSG.mbsTestQ) {
		// For testing, deliver messages to this queue instead:
		gBSSG.mbsTestQ->write(ulmsg);
	} else {
		GPRSLOG(1) << "BSSG ===> writelowside " <<ulmsg->str()<<timestr();
		gBSSG.mbsTxQ.write(ulmsg);	// normal mode; block is headed for the SGSN.
	}
}
Exemplo n.º 4
0
void PDCHL1FEC::mchStart() {
	getRadio()->setSlot(TN(),Transceiver::IGPRS);
	// Load up the GPRS filler idle burst tables in the transceiver.
	// We could use any consecutive bsn, but lets use ones around the current time
	// just to make sure they get through in case someone is triaging somewhere.
	// Sending all 12 blocks is 2x overkill because the modulus in Transceiver::setModulus
	// for type IGPRS is set the same as type I which is only 26, not 52.
	RLCBSN_t bsn = FrameNumber2BSN(gBTS.time().FN()) + 1;
	for (int i = 0; i < 12; i++, bsn = bsn + 1) {
		GPRSLOG(1) <<"sendIdleFrame"<<LOGVAR2("TN",TN())<<LOGVAR(bsn)<<LOGVAR(i);
		mchDownlink->sendIdleFrame(bsn);
	}
	mchOldFec->setGPRS(true,this);
	debug_test();
}
Exemplo n.º 5
0
static int opensock(uint32_t sgsnIp, int sgsnPort /*,int bssgPort*/ )
{
	//int fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	int sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (sockfd < 0) {
		LOG(ERR) << "Could not create socket for BSSGP";
		return -1;
	}


	/******
	{	// We dont want to bind here.  connect will pick a port for us.
		int32_t bssgIp = INADDR_ANY;
		struct sockaddr_in myAddr;
		memset(&myAddr,0,sizeof(myAddr));	// be safe.
		myAddr.sin_family = AF_INET;
		myAddr.sin_addr.s_addr = htonl(bssgIp);
		myAddr.sin_port = htons(bssgPort);
		if (0 != bind(sockfd,(sockaddr*)&myAddr,sizeof(myAddr))) {
			LOG(ERR) << "Could not bind NS socket to" 
				<< LOGVAR(bssgIp) << LOGVAR(bssgPort) << LOGVAR(errno);
			close(sockfd);
			return -1;
		}
	}
	****/

	struct sockaddr_in sgsnAddr;
	memset(&sgsnAddr,0,sizeof(sgsnAddr));
	sgsnAddr.sin_family = AF_INET;
	sgsnAddr.sin_addr.s_addr = sgsnIp;	// This is already in network order.
	sgsnAddr.sin_port = htons(sgsnPort);
	if (0 != connect(sockfd,(sockaddr*)&sgsnAddr,sizeof(sgsnAddr))) {
		LOG(ERR) << "Could not connect NS socket to"
			<< LOGVAR(sgsnIp) << LOGVAR(sgsnPort) << LOGVAR(errno);
		close(sockfd);
		return -1;
	} else {
		GPRSLOG(1) << "connected to SGSN at "<< inet_ntoa(sgsnAddr.sin_addr) <<" port "<<sgsnPort;
	}
	return sockfd;
}
Exemplo n.º 6
0
// OLD: Send this loop an NS_BLOCK message to kill this thread off;
// and we dont normally use that NS message.
// There is a BSSG-level BVC_BLOCK message that we would use to do a temporary data block.
void *sendServiceLoop(void *arg)
{
	BSSGMain *bssgp = (BSSGMain*)arg;
	NSPDUType::type nstype = NSPDUType::NS_RESET;	// init to anything.
	do {
		NSMsg *ulmsg = bssgp->mbsTxQ.read();
		// It is already wrapped up in an NS protocol.
		int msgsize = ulmsg->size();
		ssize_t result = send(bssgp->mbsSGSockfd,ulmsg->begin(),msgsize,0);
		nstype = ulmsg->getNSPDUType();
		int debug_level = 1; //(nstype == NSPDUType::NS_UNITDATA) ? 1 : 4;
		if (GPRS::GPRSDebug & debug_level) {
			GPRSLOG(debug_level) << "BSSG ===> sendServiceLoop sent "
				<<nstype<<LOGVAR(msgsize)<<ulmsg->str()<<timestr();
		}
		if (result != msgsize) {
			LOG(ERR) << "BSSGP invalid send result" << LOGVAR(result) << LOGVAR(msgsize);
		}
		delete ulmsg;
	} while (bssgp->mbsIsOpen /*&& nstype != NSPDUType::NS_BLOCK*/);
	return NULL;
}
Exemplo n.º 7
0
bool BSSGMain::BSSGReset()
{
	// BSSG starts out blocked until it receives a reset.
	mbsBlocked = true;
	mbsResetReceived = false;
	mbsResetAckReceived = false;

	// Start communication with SGSN.
	// Initiate NS Reset procedure: GSM 08.16 7.3
	// We are supposed to send NS_STATUS, but it doesnt matter with our sgsn.
	BSSGWriteLowSide(NsFactory(NSPDUType::NS_RESET));
	BSSGWriteLowSide(NsFactory(NSPDUType::NS_UNBLOCK));
	// Wait a bit for the sgsn to respond.
	// Note: The first time we talk to the SGSN it sends us an NS_RESET,
	// but if OpenBTS crashes, the second time it inits it doesnt send NS_RESET,
	// which may be a bug in the SGSN, but in any event we dont want
	// to wait for a RESET msg.
	for (int i = 0; 1; i++) {
		if (mbsResetAckReceived && !mbsBlocked) { break; }
		Utils::sleepf(0.1);

		if (i >= 40) {	// wait 4 seconds
			GPRSLOG(1) << LOGVAR(mbsResetReceived)
				<<LOGVAR(mbsResetAckReceived) <<LOGVAR(mbsBlocked);
			LOG(INFO) << "SGSN failed to respond\n";
			return false;
		}
	}

	// GSM 08.18 8.4: Reset the BVC.  You must do this after verifying NS layer is working.
	// Must reset each BVCI separately.
	// I'm not going to bother to check for acks - if the NS protocol inited,
	// the sgsn is fine and this will init ok too.
	BSSGWriteLowSide(BVCFactory(BSPDUType::BVC_RESET,BVCI::SIGNALLING));
	BSSGWriteLowSide(BVCFactory(BSPDUType::BVC_RESET,gBSSG.mbsBVCI));
	return true;
}
Exemplo n.º 8
0
void NsRecvMsg(unsigned char *data, int nsize)
{
	NSPDUType::type nstype = (NSPDUType::type) data[0];
	// We dont need to see all the keep alive messages.
	if (nstype != NSPDUType::NS_UNITDATA) { GPRSLOG(4) << "BSSG NsRecvMsg "<<nstype<<LOGVAR(nsize); }
	switch (nstype) {
	case NSPDUType::NS_UNITDATA: {
		int bvci = getntohs(&data[2]);
		if (bvci == BVCI::SIGNALLING) {
			GPRSLOG(4) << "BSSG <=== signalling "<<nstype<<LOGVAR(nsize) <<timestr();
			BsRecvSignallingMsg(data, nsize);
		} else if (bvci == BVCI::PTM) {
			GPRSLOG(4) << "BSSG <=== "<<nstype<<LOGVAR(nsize)<<" ignored" <<timestr();
			// Not implemented
		} else {
			// Send data to the MAC
			// We left the NS header intact.
			BSSGDownlinkMsg *dlmsg = new BSSGDownlinkMsg(data,nsize);
			//GPRSLOG(1) << "BSSG <=== queued "<<dlmsg->str() <<timestr();
			GPRSLOG(1) << "BSSG <=== queued size="<<dlmsg->size() <<timestr();
			gBSSG.mbsRxQ.write(dlmsg);
		}
		break;
	}
	case NSPDUType::NS_RESET:
		gBSSG.mbsResetReceived = true;
		BSSGWriteLowSide(NsFactory(NSPDUType::NS_RESET_ACK));
		break;
	case NSPDUType::NS_RESET_ACK:
		gBSSG.mbsResetAckReceived = true;
		break;
	case NSPDUType::NS_BLOCK:
		gBSSG.mbsBlocked = true;
		BSSGWriteLowSide(NsFactory(NSPDUType::NS_BLOCK_ACK));
		break;
	case NSPDUType::NS_BLOCK_ACK:
		// ignored.
		break;
	case NSPDUType::NS_UNBLOCK:
		gBSSG.mbsBlocked = false;
		BSSGWriteLowSide(NsFactory(NSPDUType::NS_UNBLOCK_ACK));
		break;
	case NSPDUType::NS_UNBLOCK_ACK:
		gBSSG.mbsBlocked = false;
		// ignored.
		break;
	case NSPDUType::NS_STATUS:
		// This happens when the sgsn is stopped and restarted.
		// It probably happens for other reasons too, but lets just
		// assume that is what happened and reset the BSSG link.
		gBSSG.BSSGReset();
		break;
	case NSPDUType::NS_ALIVE:
		gBSSG.mbsAliveReceived = true;
		BSSGWriteLowSide(NsFactory(NSPDUType::NS_ALIVE_ACK));
		break;
	case NSPDUType::NS_ALIVE_ACK:
		gBSSG.mbsAliveAckReceived = true;
		break;
	default:
		LOG(INFO) << "unrecognized NS message received, type "<<nstype;
		break;
	}
}
Exemplo n.º 9
0
// Dispatch an RLC block on this downlink.
// This must run once for every Radio Block (4 TDMA frames or so) sent.
// It should be kept only as far enough ahead of the physical layer so that it never stalls.
// Based on: TCHFACCHL1Encoder::dispatch()
void PDCHL1Downlink::dlService()
{
	// Get right with the system clock.
	// NO: mchResync();
	static int debugCntTotal = 0, debugCntDummy = 0;
	debugCntTotal++;
	if ((GPRSDebug&512) || debugCntTotal % 1024 == 0) {
		GPRSLOG(2) << "dlService sent total="<<debugCntTotal<<" dummy="<<debugCntDummy <<" "<<this->parent();
	}

	// If gFixIdleFrame only send blocks on the even BSNs,
	// and send idle frames on the odd BSNs, to make SURE that the
	// RRBP and USF fields are cleared.
	// This means that only the odd uplink BSNs will be used for uplink data.
	// I also modified makeReservationInt to make the RRBP uplink reservations
	// only on even frames, since they will be clear of data, but
	// that is overkill for debugging.
	// For reservations all we care is that the downlink RRBP field
	// occurs only on even frames -
	// the associated uplink block will be 3-7 blocks downtime from that,
	// so could land on either even or odd frames.
	if (gFixIdleFrame && (gBSNNext & 1)) { return; }

	// I think we have to check active because the radio can get turned on/off
	// completely without our knowledge.
	// If this happens, the TBFs will expire, so we should probably destroy
	// the entire GPRS machinery and start over when we get turned back on.
	//if (! active()) {
	//	mNextWriteTime += 52;	// Wait for a PCH multiframe.
	//	gBTS.clock().wait(mNextWriteTime);
	//	return;
	//}


	// Look for a data block to send.
	// We did not queue these up in advance because the data that the engine
	// wants to send may change every time it receives an ack/nack message.
	//TBFList_t &list = gL2MAC.macTBFs;
	//TBFList_t::iterator itr = list.begin(), e = list.end();
	//for ( ; itr != e; itr++) {
		//TBF *tbf = *itr;
	TBF *tbf;
	TBFList_t::iterator itr;
	for (RListIterator<TBF*> itrl(gL2MAC.macTBFs); itrl.next(tbf,itr); ) {
		if (!tbf->canUseDownlink(this)) {
			GPRSLOG(4) <<"dlService"<<tbf<<" state "<<tbf->mtGetState()
				<<" reqch:"<<tbf->mtMS->msPacch
				<< " can not use downlink:"<<this->parent();
			continue;
		}
		TBFState::type oldstate = tbf->mtGetState();

		if (tbf->mtServiceDownlink(this)) {
			GPRSLOG(2) <<"dlService"<<tbf<<LOGVAR(oldstate)<<" state="<<tbf->mtGetState()
				<<" reqch:"<<tbf->mtMS->msPacch
				<<" using ch:"<<this->parent();
			// Move this tbf to end of the list so we may service someone else next time.
			// TODO: If the tbf is using extended dynamic uplink, all ganged
			// uplink channels are reserved at once, and so we are not sharing
			// the other ganged uplinks with other TBFs that want to use them unless
			// those TBFs also share this channel.
			gL2MAC.macTBFs.erase(itr);
			gL2MAC.macTBFs.push_back(tbf);
			if (gFixIdleFrame) { bugFixIdleFrame(); }
			return;
		}
	}

	// If nothing else, send a dummy message.
	// We have to allocate it because we allocate all messages.
	// Note that this message will have the MAC header fields USF and RRBP set by send1MsgFrame.
	RLCMsgPacketDownlinkDummyControlBlock *dummymsg = new RLCMsgPacketDownlinkDummyControlBlock();
	send1MsgFrame(NULL,dummymsg,0,MsgTransNone,NULL);
	debugCntDummy++;

	if (gFixIdleFrame) { bugFixIdleFrame(); }
}
Exemplo n.º 10
0
// WARNING: This func runs in a separate thread.
void PDCHL1Uplink::writeLowSideRx(const RxBurst &inBurst)
{
	float low, avg = inBurst.getEnergy(&low);
	//if (avg > 0.7) { OBJLOG(DEBUG) << "PDCHL1Uplink " << inBurst; }

	//ScopedLock lock(testlock);
	int burstfn = inBurst.time().FN();
	int mfn = (burstfn / 13);			// how many 13-multiframes
	int rem = (burstfn - (mfn*13));	// how many blocks within the last multiframe.
	int B = rem % 4;

	if (avg > 0.5) { GPRSLOG(256) << "FEC:"<<LOGVAR(B)<<" "<<inBurst<<LOGVAR(avg); }

	ChannelCodingType cc;
	BitVector *result = decodeLowSide(inBurst,B,mchCS14Dec,&cc);

	if (B == 3) {
		int burst_fn=burstfn-3;	// First fn in rlc block.
		RLCBSN_t bsn = FrameNumber2BSN(burst_fn);

		if (GPRSDebug) {
			PDCHL1FEC *pdch = parent();
			short *qbits = mchCS14Dec.qbits;
			BitVector cshead(mchCS14Dec.mC.head(12).sliced());

			RLCBlockReservation *res = mchParent->getReservation(bsn);
			int thisUsf = pdch->getUsf(bsn-2);
			// If we miss a reservation or usf, print it:
			int missedRes = avg>0.4 && !result && (res||thisUsf);
			if (missedRes || (GPRSDebug & (result?4:256))) {
				std::ostringstream ss;
				char buf[30];
			 	ss <<"writeLowSideRx "<<parent()
					<<(result?" === good" : "=== bad")
					<< (res?" res:" : "") <<(res ? res->str() : "")
					//<<LOGVAR(cshead)
					//<<LOGVAR2("cs",(int)mchCS14Dec.getCS())
					<<LOGVAR(cc)
					<<LOGVAR2("revusf",decodeUSF(mchCS14Dec.mC))
					<<LOGVAR(burst_fn)<<LOGVAR(bsn) 
					<<LOGVAR2("RSSI",inBurst.RSSI()) <<LOGVAR2("TE",inBurst.timingError())
					// But lets print out the USFs bracketing this on either side.
					<<getAnsweringUsfText(buf,bsn)
					//<<" AnsweringUsf="<<pdch->getUsf(bsn-2)<<" "<<pdch->getUsf(bsn-1)
					//<<" ["<<pdch->getUsf(bsn)<<"] "<<pdch->getUsf(bsn+1)<<" "<<pdch->getUsf(bsn+2)
					<<" qbits="<<qbits[0]<<qbits[1]<<qbits[2]<<qbits[3]
							   <<qbits[4]<<qbits[5]<<qbits[6]<<qbits[7]
					<<LOGVAR(low)<<LOGVAR(avg)
					;
				if (missedRes) {
					for (int i = 0; i < 4; i++) {
						// There was an unanswered reservation or usf.
						avg = mchCS14Dec.mI[i].getEnergy(&low);
						GPRSLOG(1) << "energy["<<i<<"]:"<<LOGVAR(avg)<<LOGVAR(low)<<" "
							<<mchCS14Dec.mI[i];
					}
				}
				GLOG(DEBUG)<<ss.str();
				// Make sure we see a decoder failure if it reoccurs.
				if (missedRes) std::cout <<ss.str() <<"\n";
			}
		} // if GPRSDebug

		if (result) {
			// Check clock skew for debugging purposes.
			static int cnt = 0;
			if (bsn >= gBSNNext-1) {
				if (cnt++ % 32 == 0) {
					GLOG(ERR) << "Incoming burst at frame:"<<burst_fn
						<<" is not sufficiently ahead of clock:"<<gBSNNext.FN();
					if (GPRSDebug) {
					std::cout << "Incoming burst at frame:"<<burst_fn
						<<" is not sufficiently ahead of clock:"<<gBSNNext.FN()<<"\n";
					}
				}
			}

			countGoodFrame();

			// The four frame radio block has been decoded and is in mD.
			if (gConfig.getBool("Control.GSMTAP.GPRS")) {
				// Send to GSMTAP.  Untested.
				gWriteGSMTAP(ARFCN(),TN(),gBSNNext.FN(), //GSM::TDMA_PACCH,
						frame2GsmTapType(*result),
						false,	// not SACCH
						true,	// this is an uplink.
						*result);	// The data.
			}

			mchUplinkData.write(new RLCRawBlock(bsn,*result,inBurst.RSSI(),inBurst.timingError(),cc));
		} else {
			countBadFrame();
		}
	} else {
		// We dont have a full 4 bursts yet, and we rarely care about these
		// intermediate results, but here is a way to see them:
		GPRSLOG(64) <<"writeLowSideRx "<<parent()<<LOGVAR(burstfn)<<LOGVAR(B) 
			<<" RSSI=" <<inBurst.RSSI() << " timing=" << inBurst.timingError();
	}
}
Exemplo n.º 11
0
// Return true if we send a block on the downlink.
bool PDCHL1Downlink::send1MsgFrame(
	TBF *tbf,					// The TBF sending the message, or NULL for an idle frame.
	RLCDownlinkMessage *msg,	// The message.
	int makeres,			// 0 = no res, 1 = optional res, 2 = required res.
	MsgTransactionType mttype,	// Type of reservation
	unsigned *pcounter)		// If non-null, incremented if a reservation is made.
{
	if (! setMACFields(msg,mchParent,msg->mTBF,makeres,mttype,pcounter)) {
		delete msg;	// oh well.  
		return false;	// This allows some other tbf to try to use this downlink block.
	}
	
	bool dummy = msg->mMessageType == RLCDownlinkMessage::PacketDownlinkDummyControlBlock;
	bool idle = dummy && msg->isMacUnused();
	if (idle && 0 == gConfig.getNum("GPRS.SendIdleFrames")) {
		delete msg;		// Let the transceiver send an idle frame.
		return false;	// This return value will not be checked.
	}

	if (tbf) { tbf->talkedDown(); }

	// Convert to a BitVector.  Messages always use CS-1 encoding.
	BitVector tobits(RLCBlockSizeInBits[ChannelCodingCS1]);
	msg->write(tobits);
	// The possible downlink debug things we want to see are:
	// 2: Only non-dummy messages.
	// 32: include messages with non-idle MAC header, means mUSF or mSP.
	// 1024: all messages including dummy ones.
	if (GPRSDebug) {
		if ((!dummy && (GPRSDebug&2)) || (!idle && (GPRSDebug&32)) || (GPRSDebug&1024)) {
			ByteVector content(tobits);
			GPRSLOG(2|32|1024) << "send1MsgFrame "<<parent()
				<<" "<<msg->mTBF<< " "<<msg->str()
				<< " " <<LOGVAR2("content",content);
				// The res is unrelated to the message, and confusing, so dont print it:
				//<<" "<<(res ? res->str() : "");
		}
	}

#if 0
	// The below is what went out in release 3.0:
	if (GPRSDebug & (1|32)) {
		//RLCBlockReservation *res = mchParent->getReservation(gBSNNext);
		//std::ostringstream ssres;
		//if (res) ssres << res;
		if (! idle || (GPRSDebug & 1024)) {
			//ostringstream bits;
			//tobits.hex(bits);
			//GPRSLOG(1) << "send1MsgFrame "<<msg->mTBF<< " "<<msg->str() << "\nbits:"<<tobits.hexstr();
			ByteVector content(tobits);
			GPRSLOG(1) << "send1MsgFrame "<<parent()<<" "<<msg->mTBF<< " "<<msg->str() <<" "
				<< LOGVAR2("content",content);
			// This res is unrelated to the message, and confusing, so dont print it:
				//<<" "<<(res ? res->str() : "");
		} else if (msg->mUSF) {
			GPRSLOG(32) << "send1MsgFrame "<<parent()<<" "<<msg->mTBF<< " "<<msg->str() <<" ";
				//<<" "<<(res ? res->str() : "");
		}
	}
#endif

	delete msg;
	send1Frame(tobits,ChannelCodingCS1,idle);
	return true;
}
Exemplo n.º 12
0
// Return true if we send a block on the downlink.
bool PDCHL1Downlink::send1DataFrame(  //SVGDBG
	RLCDownEngine *engdown,
	RLCDownlinkDataBlock *block,	// block to send.
	int makeres,					// 0 = no res, 1 = optional res, 2 = required res.
	MsgTransactionType mttype,	// Type of reservation
	unsigned *pcounter)
{
	//ScopedLock lock(testlock);
	TBF *tbf = engdown->getTBF();
	if (! setMACFields(block,mchParent,tbf,makeres,mttype,pcounter)) { return false; }
	// The rest of the RLC header is already set, but we did not know the tfi
	// when we created the RLCDownlinkDataBlocks (because tbf not yet attached)
	// so set tfi now that we know.  Update 8-2012: Above comment is stale because we
	// make the RLCDownlinkBlocks on the fly now.
	block->mTFI = tbf->mtTFI;
	// block->mPR = 1;	// DEBUG test; made no diff.

	tbf->talkedDown();

	BitVector tobits = block->getBitVector(); // tobits deallocated when this function exits.
	if (block->mChannelCoding == 0) { devassert(tobits.size() == 184); }
	if (GPRSDebug & 1) {
		RLCBlockReservation *res = mchParent->getReservation(gBSNNext);
		std::ostringstream sshdr;
		block->text(sshdr,false); //block->RLCDownlinkDataBlockHeader::text(sshdr);
		ByteVector content(tobits);
		GPRSLOG(1) << "send1DataFrame "<<parent()<<" "<<tbf<<LOGVAR(tbf->mtExpectedAckBSN)
			<< " "<<sshdr.str()
			<<" "<<(res ? res->str() : "")
			<< LOGVAR2("content",content);
		//<< " enc="<<tbf->mtChannelCoding <<" "<<os.str() << "\nbits:" <<bits.str();
		//<<" " <<block->str() <<"\nbits:" <<tobits.hexstr();
	}
#if FEC_DEBUG
	BitVector copybits; copybits.clone(tobits);
#endif
	send1Frame(tobits,block->mChannelCoding,0);
#if FEC_DEBUG
	BitVector *result = debugDecoder.getResult();
	devassert(result);
	devassert(copybits == tobits);
	if (result && !(*result == tobits)) {
		int diffbit = -1;
		char thing[500];
		for (int i = 0; i < (int)result->size(); i++) {
			thing[i] = '-';
			if (result->bit(i) != tobits.bit(i)) {
				if (diffbit == -1) diffbit = i;
				thing[i] = '0' + result->bit(i);
			}
		}
		thing[result->size()] = 0;
		GPRSLOG(1) <<"encoding error" <<LOGVAR2("cs",(int)debugDecoder.getCS())
			<<LOGVAR(diffbit)
			<<LOGVAR2("in:size",tobits.size()) <<LOGVAR2("out:size",result->size())
			<<"\n"<<tobits
			<<"\n"<<*result
			<<"\n"<<thing;
	} else {
		//GPRSLOG(1) <<"encoding ok" <<LOGVAR2("cs",(int)debugDecoder.getCS());
	}
#endif
	return true;
}