1934 lines
65 KiB
C++
1934 lines
65 KiB
C++
/*
|
|
* OpenBTS provides an open source alternative to legacy telco protocols and
|
|
* traditionally complex, proprietary hardware systems.
|
|
*
|
|
* Copyright 2014 Range Networks, Inc.
|
|
*
|
|
* This software is distributed under the terms of the GNU Affero General
|
|
* Public License version 3. See the COPYING and NOTICE files in the main
|
|
* directory for licensing information.
|
|
*
|
|
* This use of this software may be subject to additional restrictions.
|
|
* See the LEGAL file in the main directory for details.
|
|
*/
|
|
|
|
#define URLC_IMPLEMENTATION 1
|
|
#include "URRC.h" // For UEInfo guts
|
|
#include "URRCMessages.h"
|
|
#include "Configuration.h"
|
|
#include "MACEngine.h" // For macHeaderSize
|
|
#include "Logger.h"
|
|
#include "URLC.h"
|
|
|
|
//#define RLCLOG(stuff...) PATLOG(4, "RLC " << rlcid() <<":" << format(stuff) << mUep);
|
|
#define RLCLOG(stuff...) LOG(DEBUG) << "RLC " << rlcid() <<":" << format(stuff) << mUep;
|
|
#define RLCERR(stuff...) LOG(ERR) << "RLC "<< rlcid() <<":" << format(stuff) << mUep;
|
|
|
|
namespace UMTS {
|
|
|
|
const char*URlcMode2Name(URlcMode mode)
|
|
{
|
|
switch (mode) {
|
|
case URlcModeTm: return "TM";
|
|
case URlcModeUm: return "UM";
|
|
case URlcModeAm: return "AM";
|
|
default: assert(0);
|
|
}
|
|
}
|
|
|
|
// This is a temporary stub to send discard messages.
|
|
// This is what is defined in the spec, because normally these message would
|
|
// go to an MSC controller far away. But we will probably do not do it this way,
|
|
// we will probably have a UEInfo struct and call an appropriate function
|
|
// inside the UE if messages are discared on SRBs, which indicates that the UE
|
|
// has become disconnected.
|
|
void informL3SduLoss(URlcDownSdu *sdu)
|
|
{
|
|
PATLOG(1,"discarding sdu sizebytes="<<sdu->size());
|
|
LOG(WARNING) << "discarding sdu sizebytes="<<sdu->size();
|
|
// There, you're informed.
|
|
}
|
|
|
|
int URlcBase::deltaSN(URlcSN sn1, URlcSN sn2)
|
|
{
|
|
int ws = mSNS/2;
|
|
int delta = (int)sn1 - (int)sn2;
|
|
//assert(!(delta >= ws));
|
|
if (delta < - ws) delta += mSNS; // modulo the sequence space
|
|
if (delta > ws) delta -= mSNS;
|
|
return delta;
|
|
}
|
|
|
|
// Warning: the numbers can be negative.
|
|
URlcSN URlcBase::addSN(URlcSN sn1, URlcSN sn2)
|
|
{
|
|
return ((unsigned)((int)sn1 + (int)sn2)) % (unsigned) mSNS;
|
|
}
|
|
|
|
URlcSN URlcBase::minSN(URlcSN sn1, URlcSN sn2)
|
|
{
|
|
return (deltaSN(sn1,sn2) <= 0) ? sn1 : sn2;
|
|
}
|
|
|
|
URlcSN URlcBase::maxSN(URlcSN sn1, URlcSN sn2)
|
|
{
|
|
return (deltaSN(sn1,sn2) >= 0) ? sn1 : sn2;
|
|
}
|
|
|
|
void URlcBase::incSN(URlcSN &psn)
|
|
{
|
|
psn = addSN(psn,1);
|
|
}
|
|
|
|
void URlcPdu::appendLIandE(unsigned licnt, unsigned E, unsigned lisize)
|
|
{
|
|
if (lisize == 1) {
|
|
appendByte((licnt<<1) | E);
|
|
} else {
|
|
appendUInt16((licnt<<1) | E);
|
|
}
|
|
}
|
|
|
|
void URlcPdu::text(std::ostream &os) const
|
|
{
|
|
os <<" URlcPdu(";
|
|
switch (rlcMode()) {
|
|
case URlcModeTm:
|
|
os <<"TM" <<LOGVAR2("size",size());
|
|
break;
|
|
case URlcModeUm:
|
|
os <<"UM" <<LOGVAR2("sn",getSN());
|
|
os <<LOGVAR2("payloadSize",getPayloadSize());
|
|
os <<LOGVAR2("E",getEIndicator());
|
|
break;
|
|
case URlcModeAm:
|
|
if (getAmDC()) {
|
|
os <<"AM" <<LOGVAR2("sn",getSN());
|
|
os <<LOGVAR2("payloadSize",getPayloadSize());
|
|
os <<LOGVAR2("E",getEIndicator());
|
|
} else {
|
|
switch ((int)getField(1,3)) {
|
|
case 0: os <<"AM control type=status sufi="<<(int)getField(4,4); break;
|
|
case 1: os <<"AM control type=reset"; break;
|
|
case 2: os <<"AM control type=reset ack"; break;
|
|
default: os <<"AM control type=unrecognized"; break;
|
|
}
|
|
}
|
|
}
|
|
os <<"\"" <<mDescr <<"\"";
|
|
os <<")";
|
|
}
|
|
|
|
// This pops out of the high side of the RLC.
|
|
struct RrcUplinkMessage {
|
|
ByteVector *msgSdu; // memory is managed by the RrcUplinkMessage
|
|
UEInfo *msgUep;
|
|
RbId msgRbid;
|
|
RrcUplinkMessage(ByteVector *wSdu, UEInfo *wUep, RbId wRbid) : msgSdu(wSdu), msgUep(wUep), msgRbid(wRbid) {}
|
|
virtual ~RrcUplinkMessage() { if (msgSdu) delete msgSdu; }
|
|
};
|
|
|
|
// The high side of the RLCs stuffs messages into these loops, then the threads below pass them on.
|
|
// All started by l2RlcStart()
|
|
static InterthreadQueue<RrcUplinkMessage> gRrcUplinkQueue;
|
|
static InterthreadQueue<RrcUplinkMessage> gSgsnUplinkQueue;
|
|
static Thread mSgsnUplinkThread;
|
|
static Thread mRrcUplinkThread;
|
|
|
|
static void *rrcUplinkServiceLoop(void *arg)
|
|
{
|
|
while (1) {
|
|
RrcUplinkMessage *msg = gRrcUplinkQueue.read();
|
|
msg->msgUep->ueRecvDcchMessage(*msg->msgSdu,msg->msgRbid);
|
|
delete msg;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void *sgsnUplinkServiceLoop(void *arg)
|
|
{
|
|
while (1) {
|
|
RrcUplinkMessage *msg = gSgsnUplinkQueue.read();
|
|
msg->msgUep->ueRecvData(*msg->msgSdu,msg->msgRbid);
|
|
delete msg;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void l2RlcStart()
|
|
{
|
|
mSgsnUplinkThread.start(sgsnUplinkServiceLoop,0);
|
|
mRrcUplinkThread.start(rrcUplinkServiceLoop,0);
|
|
}
|
|
|
|
// Send an sdu out the high side of the rlc, routed based on rbid.
|
|
void URlcRecv::rlcSendHighSide(URlcUpSdu *sdu)
|
|
{
|
|
if (this->mHighSideFunc) {
|
|
// This is used for testing.
|
|
this->mHighSideFunc(*sdu,mrbid);
|
|
delete sdu;
|
|
return;
|
|
}
|
|
switch (this->mrbid) {
|
|
case 0:
|
|
// Probably RBInfo misconfiguration.
|
|
// 12-29-2012 pat: This assertion is occurring; I think maybe we are deleting an AM-RLC
|
|
// while it is still in use by the uplink.
|
|
// The SRB0 messages were supposed to go to the RLC in the MAC-C.
|
|
RLCERR("invalid RLC high side message to SRB0, probably RB mis-configuration");
|
|
assert(0);
|
|
case 1: case 2: case 3:
|
|
// case 1 and 2 are messages to RRC.
|
|
// case 3 is a Layer3 message, but handled the same to crack it out of its RRC message wrapper.
|
|
gRrcUplinkQueue.write(new RrcUplinkMessage(sdu,mUep,mrbid));
|
|
break;
|
|
default:
|
|
if (mrbid >= 16) {
|
|
// RLC mis-configuration.
|
|
assert(0);
|
|
}
|
|
// User data.
|
|
gSgsnUplinkQueue.write(new RrcUplinkMessage(sdu,mUep,mrbid));
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if 0 // previous code. Can delete.
|
|
void URlcRecv::rlcSendHighSide(URlcUpSdu *sdu)
|
|
{
|
|
if (this->mHighSideFunc) {
|
|
// This is used for testing.
|
|
this->mHighSideFunc(*sdu,mrbid);
|
|
delete sdu;
|
|
return;
|
|
}
|
|
|
|
switch (this->mrbid) {
|
|
case 0:
|
|
// Probably RBInfo misconfiguration.
|
|
// The SRB0 messages were supposed to go to the RLC in the MAC-C.
|
|
RLCERR("invalid RLC high side message to SRB0, probably RB mis-configuration");
|
|
assert(0);
|
|
case 1: case 2:
|
|
// It is a message to RRC.
|
|
mUep->ueRecvDcchMessage(*sdu,mrbid);
|
|
delete sdu;
|
|
break;
|
|
case 3:
|
|
// It is a Layer3 message, but handled the same to crack it out of its RRC message wrapper.
|
|
mUep->ueRecvDcchMessage(*sdu,mrbid);
|
|
delete sdu;
|
|
break;
|
|
default:
|
|
if (mrbid >= 16) {
|
|
// RLC mis-configuration.
|
|
assert(0);
|
|
}
|
|
// User data.
|
|
mUep->ueRecvData(*sdu,mrbid);
|
|
delete sdu;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
// This is already associated with a specific RAB by being
|
|
// called within this particular URlcUMT
|
|
// Clause 8.1: The parameters are: Data, CNF Confirmation Request,
|
|
// which we wont implement, DiscardReq, which we will,
|
|
// MUI Message Unit Id for this sdu in confirm/discard messages back to layer3,
|
|
// and UE-Id type indicator (C-RNTI or U-RNTI), about which I have no clue yet,
|
|
// and doesnt make sense for what I know - the C-RNTI is only used on phy, not up here.
|
|
// Update: maybe the ue-id type indicator is used when the controlling RNC != serving RNC, which we never do.
|
|
// nope: We're going to use CNF for both Confirmation and Discard requests.
|
|
void URlcTrans::rlcWriteHighSide(ByteVector &data, bool DiscardReq, unsigned MUI,string descr)
|
|
{
|
|
RLCLOG("rlcWriteHighSide sizebytes=%d rbid=%d descr=%s",
|
|
data.size(),mrbid,descr.c_str());
|
|
|
|
// pat 12-17: Changed the GGSN to pre-allocate this so we dont have to do it here.
|
|
//ByteVector cloneData;
|
|
//cloneData.clone(data);
|
|
//URlcDownSdu *sdu = new URlcDownSdu(cloneData,DiscardReq,MUI,descr);
|
|
URlcDownSdu *sdu = new URlcDownSdu(data,DiscardReq,MUI,descr);
|
|
RN_MEMLOG(URlcDownSdu,sdu);
|
|
ScopedLock lock(mQLock);
|
|
//printf("pushing SDU of size: %u, addr: %0x, descr=%s\n",data.size(),sdu,descr.c_str());
|
|
mSduTxQ.push_back(sdu);
|
|
|
|
LOG(INFO) << "Bytes avail: " << rlcGetSduQBytesAvail();
|
|
// Check for buffer overflow.
|
|
if (!mSplitSdu && rlcGetSduQBytesAvail() > mTransmissionBufferSizeBytes)
|
|
{
|
|
// Throw away sdus. We toss the oldest sdu that is not in progress,
|
|
// which means just toss them off the front of the queue.
|
|
URlcDownSdu *sdu = NULL;
|
|
|
|
// Delete all but one of the sdus.
|
|
// We leave one sdu in the queue to mark the spot where deletions ocurred.
|
|
//LOG(INFO) << "Bytes avail: " << rlcGetSduQBytesAvail();
|
|
while (mSduTxQ.size() && rlcGetSduQBytesAvail() > mTransmissionBufferSizeBytes) {
|
|
//if (sdu) { sdu->free(); mVTSDU++; }
|
|
if (sdu) { sdu->free(); mVTSDU++; }
|
|
sdu = mSduTxQ.pop_front();
|
|
LOG(INFO) << "Discarding sdu %0x ," << sdu << " TxQ size: " << rlcGetSduQBytesAvail();
|
|
if (!sdu->mDiscarded) {informL3SduLoss(sdu);}
|
|
}
|
|
|
|
// Shove the last deleted sdu back in the queue to mark the spot.
|
|
// We null out mData to indicate that one or more sdus were deleted at this spot.
|
|
//if (sdu->mData) {
|
|
//delete sdu->mData;
|
|
//sdu->mData = NULL;
|
|
//}
|
|
//sdu->mDiscarded = true;
|
|
//mSduTxQ.push_front(sdu);
|
|
}
|
|
}
|
|
|
|
// About the LI Length Indicator field.
|
|
// If the SDU exactly fills the PDU and there is no room for an LI field,
|
|
// you set the LI in the subsequent PDU as follows:
|
|
// - If the next PDU is exactly filled by a complete SDU (remembering that the length
|
|
// of the following PDU is less 1 to make room for this extra LI field)
|
|
// then use LI=0x7d, otherwise, LI=0.
|
|
// Note that the PDU must be sent even it if has not SDU data.
|
|
// If the PDU has padding, add LI=0x7f after the final length indicator.
|
|
// The following is mandatory for uplink, and 11.2.3.1 implies that it is optional in downlink.
|
|
// Every PDU that starts a new SDU and doesnt have any of the other special LI values,
|
|
// must start with LI=0x7c, so the nodeB can tell it is the start of a packet even if it did not
|
|
// receive the previous PDU.
|
|
|
|
|
|
|
|
// Common routine for Am and Um modes to fill the data part of the downlink pdu.
|
|
// The Am and Um pdus have different header sizes, which is passed in pduHeaderSize.
|
|
// The result is a discard notification which is true if UM sdus were discarded.
|
|
// The 'alternate interpretation of HE' is a wonderful thing that indicates
|
|
// the end of the SDU and gets rid of many of the special cases below,
|
|
// but unfortunately it only exists in AM, and we still need the special
|
|
// cases for UM, so I did not implement it.
|
|
bool URlcTransAmUm::fillPduData(URlcPdu *result,
|
|
unsigned pduHeaderSize,
|
|
bool *pNewSdu) // Set if this pdu is the start of a new sdu.
|
|
{
|
|
ScopedLock lock(mQLock);
|
|
|
|
// Step one: how many sdus can we fit in this pdu?
|
|
// remaining = How many bytes left in output PDU.
|
|
unsigned remaining = result->size() - pduHeaderSize;
|
|
|
|
bool mSduDiscarded = false; // Set if discarded SDU is detected.
|
|
|
|
unsigned sducnt = 0; // Number of whole or partial sdus sent.
|
|
unsigned sdufinalbytes = 0; // If non-zero, final sdu is split and this number of bytes sent.
|
|
unsigned vli[100]; // We will gather up the LI indicators as we go.
|
|
unsigned libytes = mConfig->mDlLiSizeBytes;
|
|
int licnt = 0;
|
|
|
|
URlcDownSdu *sdu = mSplitSdu ? mSplitSdu : mSduTxQ.front();
|
|
bool newSdu = sdu && !mSplitSdu; // We are starting a new sdu.
|
|
if (pNewSdu) {*pNewSdu = newSdu;}
|
|
|
|
if (sdu == NULL) {
|
|
// This special case occurs if the previous sdu exactly filled the
|
|
// the pdu, but we need an indication in the next pdu (which is the
|
|
// one we are currently creating) to mark the end of that sdu,
|
|
// but the incoming sdu queue is empty.
|
|
// We need to output one pdu with only li flags, no data, to notify
|
|
// the receiver on the other end of the end of the sdu.
|
|
if (mLILeftOver) {
|
|
remaining -= libytes; // For the LI field we are about to add.
|
|
vli[licnt++] = (mLILeftOver == 1) ? 0 : 0x7ffb;
|
|
mLILeftOver = 0;
|
|
} else {
|
|
assert(0); // Caller prevents this by checking pdusFinished first.
|
|
}
|
|
result->mDescr = string("li_only");
|
|
} else {
|
|
result->mDescr += sdu->mDescr;
|
|
}
|
|
|
|
URlcDownSdu *sdunext = NULL;
|
|
for ( ; sdu; sdu = sdunext) {
|
|
if (! mConfig->mIsSharedRlc) { // SharedRlc only sends one SDU at a time.
|
|
sdunext = (sdu == mSplitSdu) ? mSduTxQ.front() : sdu->next();
|
|
}
|
|
if (sdu->mDiscarded) {
|
|
// SDU was discarded, possibly because transmission buffer full.
|
|
if (mRlcMode == URlcModeUm) {
|
|
// Perform function of 11.2.4.3: SDU discard without explicit signaling.
|
|
// Also applies to the case of SDU discard not configured when transmission buffer
|
|
// is full and a partially sent SDU is discarded as per 9.7.3.5, however,
|
|
// that depends on whether we decide to discard in-progress sdus or not.
|
|
// I am going to not do that, since the in-progress sdu probably represents
|
|
// usable data on the other end.
|
|
if (mSduDiscarded == false) {
|
|
mSduDiscarded = true;
|
|
if (mConfig->mRlcDiscard.mSduDiscardMode != TransmissionRlcDiscard::NotConfigured) {
|
|
if (sducnt) {
|
|
// We need to inform the peer where the missing SDU occurred
|
|
// so we cant put back-to-back SDUs into the PDU with
|
|
// the missing SDU (not) between them, so stop now.
|
|
// And leave the discarded SDU here so we will see it next time.
|
|
break;
|
|
} else {
|
|
// Force fillPduData to add an extra first Length Indicator
|
|
// to indicate start of SDU.
|
|
mLILeftOver = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Skip over the discarded sdu.
|
|
mVTSDU++;
|
|
sdu->free();
|
|
continue;
|
|
}
|
|
|
|
sducnt++; // We will output all or part of this sdu.
|
|
unsigned sdusize = sdu->size(); // Size of the whole remaining SDU.
|
|
|
|
// Note: a non-zero mLILeftOver flag here implies this is the start of a new SDU,
|
|
// but not the reverse, ie, mLILeftOver == 0 says nothing about start of SDU.
|
|
if (mConfig->UM.mAltEBitInterpretation) {
|
|
// We dont use "alternative-E bit interpretation".
|
|
// The following special LI values are used only with "alternative E-bit interpretation"
|
|
// 0x7ffa (1010), 0x7ffd (1101), 0x7ffe (1110) as first LI field
|
|
assert(0);
|
|
} else {
|
|
// Handle the special case of extra LI field to mark previous PDU.
|
|
// The previous PDU ended on, or 1 byte short of, the end of the PDU.
|
|
if (mLILeftOver) {
|
|
assert(licnt == 0);
|
|
remaining -= libytes; // For the LI field we are about to add.
|
|
vli[licnt++] = (mLILeftOver == 1) ? 0 : 0x7ffb;
|
|
mLILeftOver = 0;
|
|
}
|
|
}
|
|
|
|
// And now, back to our originally scheduled programming:
|
|
// Notes: the 0x7ffc (1100) special LI value is only used in uplink, not downlink.
|
|
// 5-15-2012: However, we are going to try putting it in anyway; the spec says
|
|
// the UE 'should be prepared to receive it' on downlink.
|
|
// We might want to do this only if mConfig->mIsSharedRlc
|
|
if ((result->rlcMode() == URlcModeUm) && (licnt == 0 && newSdu) ) {
|
|
remaining -= libytes; // For the LI field we are about to add.
|
|
vli[licnt++] = 0x7ffc;
|
|
}
|
|
|
|
if (sdusize > remaining) {
|
|
sdufinalbytes = remaining;
|
|
remaining = 0;
|
|
// No LI indicator - this sdu spills over the end of the pdu into the next pdu.
|
|
//mSduIsSplit = true;
|
|
} else if (sdusize == remaining) {
|
|
mLILeftOver = 1; // Special case: LI indicator goes in next pdu.
|
|
remaining = 0;
|
|
} else if (libytes == 2 && sdusize == remaining-1) {
|
|
mLILeftOver = 2; // Special case: LI indicator goes in next pdu.
|
|
remaining = 1;
|
|
} else if (libytes == 2 && sdusize == remaining-3) {
|
|
// In above, 3 == 2-byte LI + 1 more byte, which is not enough for another LI field.
|
|
vli[licnt++] = sdusize;
|
|
remaining = 1;
|
|
} else if (libytes == 2 && sdusize == remaining+1) {
|
|
// Split pdu and put the final byte in the next pdu.
|
|
sdufinalbytes = remaining;
|
|
vli[licnt++] = remaining;
|
|
//mSduIsSplit = true;
|
|
remaining = 0;
|
|
} else {
|
|
remaining -= libytes;
|
|
assert(sdusize <= remaining); // We handled all the other special cases above.
|
|
vli[licnt++] = sdusize;
|
|
remaining -= sdusize;
|
|
}
|
|
|
|
if (remaining <= libytes) {
|
|
// Not enough room left to do anything useful.
|
|
break;
|
|
}
|
|
|
|
if (mConfig->UM.mSN_Delivery) { // This config option means do not concatenate SDUs in a PDU
|
|
break;
|
|
}
|
|
if (licnt == 100) break;
|
|
RLCLOG("fillpdu resultsize:%d sdusize=%d sdufinalbytes=%d remaining=%d",result->size(),sdusize,sdufinalbytes,remaining);
|
|
} // for sdu
|
|
|
|
if (remaining >= libytes) {
|
|
result->mPaddingLILocation = pduHeaderSize + licnt * libytes;
|
|
result->mPaddingStart = result->size() - remaining;
|
|
vli[licnt++] = 0x7fff; // Mark padding
|
|
remaining -= libytes;
|
|
} else {
|
|
result->mPaddingLILocation = 0; // This is an impossible value.
|
|
result->mPaddingStart = 0;
|
|
}
|
|
|
|
// Create the result ByteVector, add the LI+E fields.
|
|
// E==1 implies a following LI+E field.
|
|
// We dont implement the "Alternative E-bit interpretation".
|
|
result->setEIndicator(licnt ? 1 : 0); // Set the E or HE bit in the header.
|
|
result->setAppendP(pduHeaderSize);
|
|
if (licnt) {
|
|
for (int i = 0; i < licnt; i++) {
|
|
result->appendLIandE(vli[i],i != licnt-1,libytes);
|
|
}
|
|
RLCLOG("fillpdu after adding li, headersize:%d",result->size());
|
|
} else {
|
|
RLCLOG("fillpdu no li, headersize:%d",result->size());
|
|
}
|
|
|
|
// Step two: add the data from sducnt SDUs to the result PDU.
|
|
for (unsigned n = 0; n < sducnt; n++) {
|
|
if (mSplitSdu) {
|
|
sdu = mSplitSdu; mSplitSdu = NULL;
|
|
} else {
|
|
sdu = mSduTxQ.pop_front();
|
|
}
|
|
|
|
// Need these checks to assure the mSplitSdu didn't just get discarded b/c mSduTxQ is too big.
|
|
if (!sdu) {LOG(NOTICE) << "NULL Pointer in mSduTxQ"; break;} // This is a bug.
|
|
if (!sdu->sduData()->size()) { LOG(INFO) << "Empty SDU in mSduTxQ"; break;} // This is a bug.
|
|
// If this is the final sdu and it is a partial one:
|
|
|
|
if (n+1 == sducnt && sdufinalbytes) {
|
|
// Copy part of this sdu.
|
|
//LOG(INFO) << "sduData: " << *(sdu->sduData());
|
|
result->append(sdu->sduData()->begin(),sdufinalbytes);
|
|
//printf("sdu->sduData(): %0x\n",sdu->sduData());
|
|
sdu->sduData()->trimLeft(sdufinalbytes);
|
|
mSplitSdu = sdu;
|
|
RLCLOG("fillpdu appending (partial) %d sdu bytes, result=%d bytes",
|
|
sdufinalbytes, result->size());
|
|
} else {
|
|
// Copy the entire SDU.
|
|
result->append(sdu->sduData());
|
|
RLCLOG("fillpdu appending %d sdu bytes, result=%d bytes",
|
|
sdu->sduData()->size(), result->size());
|
|
mVTSDU++;
|
|
sdu->free();
|
|
//printf("Freeing SDU: %0x\n",sdu);
|
|
/*** We now pad the entire remaining with 0, below.
|
|
if (n+1 == sducnt && result->size() == dataSize-1) {
|
|
// Special case of 1 extra byte: It is filled with 0.
|
|
// "In the case where a PDU contains a 15-bit "Length Indicator" indicating
|
|
// that an RLC SDU ends with one octet left in the PDU,
|
|
// the last octet of this PDU shall:
|
|
// - be padded by the sender and ignored by the Receiver though there is no
|
|
// "Length Indicator" indicating the existence of padding.
|
|
result->appendByte(0);
|
|
}
|
|
***/
|
|
}
|
|
}
|
|
|
|
if (remaining) {
|
|
result->appendFill(0,remaining);
|
|
}
|
|
mVTPDU++;
|
|
return mSduDiscarded;
|
|
}
|
|
|
|
// MAC reads the low side with this.
|
|
// Caller is responsible for deleting this.
|
|
URlcBasePdu *URlcTransAmUm::rlcReadLowSide()
|
|
{
|
|
URlcPdu *pdu;
|
|
if (mRlcState == RLC_STOP) {return NULL;}
|
|
bool wasqueued = true;
|
|
if (! (pdu = mPduOutQ.readNoBlock())) {
|
|
pdu = readLowSidePdu();
|
|
wasqueued = false;
|
|
}
|
|
if (pdu) RLCLOG("readlLowSide(q=%d,sizebytes=%d,payloadsize=%d,descr=%s,rb=%d header=%s)",
|
|
wasqueued,pdu->size(),pdu->getPayloadSize(),
|
|
pdu->mDescr.c_str(),mrbid,pdu->segment(0,2).hexstr().c_str());
|
|
|
|
return pdu;
|
|
}
|
|
|
|
// Just turn the SDU into a PDU and send it along.
|
|
// Caller is responsible for deleting this.
|
|
URlcBasePdu *URlcTransTm::rlcReadLowSide()
|
|
{
|
|
ScopedLock lock(mQLock);
|
|
if (mRlcState == RLC_STOP) {return NULL;}
|
|
if (!mSplitSdu) {
|
|
while (URlcDownSdu *sdu = mSduTxQ.pop_front()) {
|
|
if (sdu->mDiscarded) {
|
|
sdu->free();
|
|
} else {
|
|
RLCLOG("readlLowSide(sizebits=%d,descr=%s,rb=%d)",
|
|
sdu->sizeBits(), sdu->mDescr.c_str(),mrbid);
|
|
return sdu;
|
|
}
|
|
}
|
|
}
|
|
return NULL; // Shouldnt happen - MAC should check q size first.
|
|
}
|
|
|
|
// Pull data through the RLC to fill the output queue, up to the specified amt,
|
|
// which is the maximum amount needed for any Transport Format.
|
|
void URlcTransAmUm::rlcPullLowSide(unsigned amt)
|
|
{
|
|
URlcPdu *vec;
|
|
int cnt = 0;
|
|
while (mPduOutQ.totalSize() < amt && ((vec = readLowSidePdu())) ) {
|
|
mPduOutQ.write(vec);
|
|
cnt++;
|
|
//LOG(INFO) << "amt: " << amt << " sz: " << mPduOutQ.totalSize();
|
|
}
|
|
|
|
if (cnt) LOG(INFO) << format("rlcPullLowSide rb%d amt=%d sent %d pdus, pduq=%d(%dB), sduq=%d(%dB)",
|
|
mrbid,amt,cnt,mPduOutQ.size(),mPduOutQ.totalSize(),mSduTxQ.size(),mSduTxQ.totalSize());
|
|
}
|
|
|
|
URlcPdu *URlcTransUm::readLowSidePdu()
|
|
{
|
|
if (pdusFinished()) { return NULL; }
|
|
URlcPdu *result = new URlcPdu(mConfig.mDlPduSizeBytes,this,"dl um");
|
|
RN_MEMLOG(URlcPdu,result);
|
|
|
|
bool newSdu = false;
|
|
bool mSduDiscarded = fillPduData(result,1,&newSdu);
|
|
if (newSdu && mConfig.mIsSharedRlc) { mVTUS = 0; }
|
|
result->setUmSN(mVTUS);
|
|
incSN(mVTUS);
|
|
if (mSduDiscarded && mConfig.mRlcDiscard.mSduDiscardMode != TransmissionRlcDiscard::NotConfigured) {
|
|
incSN(mVTUS); // Informs peer that a discard occurred.
|
|
}
|
|
RLCLOG("readLowSidePdu sizebytes=%d",result->size());
|
|
return result;
|
|
}
|
|
|
|
URlcPdu *URlcTransAm::getDataPdu()
|
|
{
|
|
if (pdusFinished()) { return NULL; } // No data waiting in the queue.
|
|
|
|
URlcPdu *result = new URlcPdu(mConfig->mDlPduSizeBytes,parent(),"dl am");
|
|
RN_MEMLOG(URlcPdu,result);
|
|
result->fill(0,0,2); // Be safe and clear out the header.
|
|
result->setAmDC(1); // Data pdu.
|
|
// The spec says nothing about AM buffer overflow behavior except when
|
|
// in "explicit signaling" modes. If these are TCP packets, it is ok
|
|
// to just drop them, so that's what we'll do. Pretty easy:
|
|
// just ignore the fillPduData returned result.
|
|
//LOG(INFO) << "fPD time " << gNodeB.clock().get();
|
|
fillPduData(result,2,0);
|
|
//LOG(INFO) << "fPD done " << gNodeB.clock().get();
|
|
result->setAmSN(mVTS);
|
|
result->setAmP(0); // Until we know better.
|
|
RLCLOG("getDataPdu VTS=%d,VTA=%d bytes=%d header=%s dc=%d sn=%u",
|
|
(int)mVTS,(int)mVTA,result->sizeBytes(),result->segment(0,2).hexstr().c_str(),
|
|
result->getBit(0),(int)result->getField(1,12));
|
|
if (mPduTxQ[mVTS]) {
|
|
RLCERR("RLC-AM internal error: PduTxQ at %d not empty",(int)mVTS);
|
|
delete mPduTxQ[mVTS];
|
|
}
|
|
mPduTxQ[mVTS] = result;
|
|
incSN(mVTS);
|
|
return result;
|
|
}
|
|
|
|
// Return a reset or reset ack pdu depending on type.
|
|
URlcPdu *URlcTransAm::getResetPdu(PduType type)
|
|
{
|
|
RLCLOG(type == PDUTYPE_RESET ? "Sending reset pdu" : "Sending reset_ack pdu");
|
|
URlcPdu *pdu = new URlcPdu(mConfig->mDlPduSizeBytes,parent(),"dl reset");
|
|
RN_MEMLOG(URlcPdu,pdu);
|
|
pdu->fill(0);
|
|
pdu->setAppendP(0);
|
|
pdu->appendField(0,1); // DC == 0 for control pdu;
|
|
pdu->appendField(type,3);
|
|
pdu->appendField(type == PDUTYPE_RESET ? mResetTransRSN : mResetAckRSN,1);
|
|
pdu->appendField(0,3); // R1, reserved field;
|
|
pdu->appendField(parent()->mDLHFN,20);
|
|
pdu->appendZero();
|
|
return pdu;
|
|
}
|
|
|
|
bool URlcRecvAm::isReceiverOk()
|
|
{
|
|
// If mVRH < mVRR something horrible is wrong.
|
|
if (deltaSN(mVRH,mVRR) < 0) {
|
|
RLCERR("RLC out of synchronization: mVRR=%d mVRH=%d, doing reset",
|
|
(int)mVRR,(int)mVRH);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Return true if there are no more status pdus to report after this one.
|
|
bool URlcRecvAm::addAckNack(URlcPdu *pdu)
|
|
{
|
|
bool lastStatusReport = true;
|
|
bool firstStatusReport;
|
|
|
|
// mStatusSN is the mVRR at the time the status was triggered.
|
|
// Bring the mStatusSN up to date in case additional pdus were received
|
|
// between the pdu that triggered the status and now, which
|
|
// is when a pdu is being transmitted with that status.
|
|
firstStatusReport = (deltaSN(mVRR,mStatusSN) >= 0);
|
|
if (firstStatusReport) {
|
|
mStatusSN = mVRR;
|
|
}
|
|
|
|
if (mVRR != mVRH) {
|
|
assert(deltaSN(mVRH,mVRR) > 0); // by definition VRH >= VRR
|
|
assert(mPduRxQ[mVRR] == NULL); // mVRR is last in-sequence pdu received+1
|
|
URlcSN end = mVRH; // SN+1 of highest pdu known.
|
|
URlcSN sn = mStatusSN;
|
|
// If this happens the RLC is hopelessly out of synchronization aka a bug.
|
|
// We catch this case out readLowSidePdu2() and reset the connection.
|
|
|
|
// Gather up ranges of blocks that have not been received.
|
|
// 9.2.2.11.4 List SUFI. Each one can acknowledge up to 15 missing PDU ranges.
|
|
// The outer while loop stuffs as many of those into the PDU as will fit.
|
|
// Each range low[n] to low[n]+cnt[n] is a series of PDUs that have not been received.
|
|
int n, low[15], cnt[15];
|
|
bool found = false;
|
|
while (sn != end) {
|
|
// The final ACK needs 2 bytes and each LIST SUFI takes 1 + n*2 bytes.
|
|
int maxN = ((int)pdu->sizeRemaining() - 3)/2;
|
|
if (maxN > 15) { maxN = 15; } // Max number per LIST SUFI.
|
|
if (maxN == 0) {break;}
|
|
|
|
//printf("START maxN=%d sizeBits=%d sizeRemaining=%d\n",maxN,pdu->sizeBits(),pdu->sizeRemaining());
|
|
for (n = 0; n < maxN && sn != end; n++) {
|
|
// Scan forward until we find an unreceived PDU, or are finished.
|
|
// We will already be sitting on an unreceived PDU the first time
|
|
// through this loop (because sn == mVRR) or if the max cnt was reached below.
|
|
for (; sn != end && mPduRxQ[sn]; incSN(sn)) { continue; }
|
|
if (sn == end) { break; }
|
|
low[n] = sn;
|
|
cnt[n] = 1;
|
|
// Scan forward looking for a received PDU.
|
|
for (incSN(sn); sn != end && !mPduRxQ[sn] && cnt[n] < 16; incSN(sn)) {
|
|
cnt[n]++; // sn is another adjacent unreceived PDU.
|
|
}
|
|
}
|
|
if (n) {
|
|
// Output the List SUFI.
|
|
// example output: SUFI_LIST=3, n=1, low=0,0,1, cnt=0
|
|
pdu->appendField(SUFI_LIST,4);
|
|
pdu->appendField(n,4);
|
|
char debugmsg[400], *cp = debugmsg;
|
|
//printf("BEFORE sizeBits=%d sizeRemaining=%d\n",pdu->sizeBits(),pdu->sizeRemaining());
|
|
cp += sprintf(cp,"Ack Sufi mVRR=%d mVRH=%d missing:",(int)mVRR,(int)mVRH);
|
|
for (int i = 0; i < n; i++) {
|
|
// The length field in the sufi is cnt-1, ie, 0 indicates
|
|
// that only one pdu was missing.
|
|
pdu->appendField(low[i],12);
|
|
pdu->appendField(cnt[i]-1,4);
|
|
cp += sprintf(cp," %d",low[i]);
|
|
if (cnt[i]>1) { cp += sprintf(cp,"-%d(%d pdus)",low[i]+cnt[i]-1,cnt[i]);}
|
|
|
|
//RLCLOG("AFTER i=%d sizeBits=%d sizeRemaining=%d\n",i,pdu->sizeBits(),pdu->sizeRemaining());
|
|
}
|
|
RLCLOG("%s",debugmsg);
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
if (sn != mVRH) {
|
|
// There are more status reports to transmit.
|
|
lastStatusReport = false;
|
|
}
|
|
mStatusSN = sn;
|
|
|
|
if (!found && firstStatusReport) {
|
|
// This can not happen on the first acknack, but may happen
|
|
// on subsequent ones because the pdu that was left to report
|
|
// has been received in the intervening period.
|
|
RLCLOG("Ack phase error mVRR=%d mVRH=%d but no missing pdus found\n",(int)mVRR,(int)mVRH);
|
|
}
|
|
} else {
|
|
// Everything is up to date.
|
|
RLCLOG("Ack Sufi mVRR=%d mVRH=%d all ok\n",(int)mVRR,(int)mVRH);
|
|
assert(firstStatusReport && lastStatusReport);
|
|
}
|
|
// 9.2.2.11.2 Ack SUFI is the last SUFI in the Status PDU.
|
|
// The LSN field here specifies that we have received all the PDUS up to
|
|
// LSN except the ones in the Lists we added above.
|
|
// It sets VTA (SN+1 of last positively acked PDU) in the UE transmitter.
|
|
// If this status pdu does not include all the unacked blocks, we
|
|
// are required to set it to VRR.
|
|
// The LSN is confusing because in our case, a binary bit would have sufficed.
|
|
// However, note that if the acks will not fit in a PDU, you are allowed to
|
|
// send them in multiple PDUs, in which case the LSN would be useful.
|
|
// But we are not doing that.
|
|
pdu->appendField(SUFI_ACK,4);
|
|
//pdu->appendField(mStatusSN == mVRH ? mVRH : mVRR,12);
|
|
pdu->appendField((firstStatusReport && lastStatusReport) ? mVRH : mVRR,12);
|
|
return lastStatusReport;
|
|
}
|
|
|
|
URlcPdu *URlcTransAm::getStatusPdu()
|
|
{
|
|
URlcPdu *pdu = new URlcPdu(mConfig->mDlPduSizeBytes,parent(),"dl status");
|
|
RN_MEMLOG(URlcPdu,pdu);
|
|
pdu->fill(0);
|
|
pdu->setAppendP(0);
|
|
pdu->appendField(0,1); // DC == 0 for control pdu;
|
|
pdu->appendField(PDUTYPE_STATUS,3);
|
|
// Now the sufis.
|
|
if (receiver()->addAckNack(pdu)) {
|
|
mStatusTriggered = false;
|
|
}
|
|
|
|
// Zero fill to a byte boundary:
|
|
pdu->appendZero();
|
|
return pdu;
|
|
}
|
|
|
|
// The SUFI received by the receiver advances VTA in the transmitter.
|
|
// PDUs up to sn (or sn+1?) have been acknowledged by the peer entity.
|
|
void URlcTransAm::advanceVTA(URlcSN newvta)
|
|
{
|
|
for ( ; deltaSN(mVTA,newvta) < 0; incSN(mVTA)) {
|
|
if (mPduTxQ[mVTA]) { delete mPduTxQ[mVTA]; mPduTxQ[mVTA] = NULL; }
|
|
}
|
|
}
|
|
|
|
// The status pdu with the SUFIs is received by the receiving entity but the information
|
|
// applies to and is processed by the transmitting entity.
|
|
// Side effect: if we receive an acknowledgement set mNackedBlocksWaiting and
|
|
// mVSNack to the oldest negatively acknowledged block, if any.
|
|
// SUFI 25.322 9.2.2.11
|
|
void URlcTransAm::processSUFIs2(
|
|
ByteVector *vec, // May come from a status pdu or a piggy-backed status pdu.
|
|
size_t rp) // Bit position where the sufis start in vec; they are not byte-aligned.
|
|
{
|
|
unsigned i, j;
|
|
URlcSN sn;
|
|
URlcSN newva = mVTA; // SN of oldest block nacked by this pdu.
|
|
bool newvaValid = false;
|
|
RLCLOG("Sufis before: VTS=%d VTA=%d VSNack=%d NackedBlocksWaiting=%d",
|
|
(int)mVTS,(int)mVTA,(int)mVSNack,mNackedBlocksWaiting);
|
|
while (1) {
|
|
SufiType sufitype = (SufiType) vec->readField(rp,4);
|
|
switch (sufitype) {
|
|
case SUFI_NO_MORE:
|
|
return;
|
|
case SUFI_WINDOW:
|
|
// The minimum and maximum values are set by higher layers, which is us,
|
|
// so this should not happen if we set them the same.
|
|
sn = vec->readField(rp,12);
|
|
mVTWS = sn;
|
|
continue;
|
|
case SUFI_ACK: {
|
|
URlcSN lsn = vec->readField(rp,12);
|
|
if (!newvaValid || deltaSN(lsn,newva) <= 0) {
|
|
advanceVTA(lsn);
|
|
} else {
|
|
advanceVTA(newva);
|
|
}
|
|
if (mNackedBlocksWaiting) advanceVS(true);
|
|
|
|
RLCLOG("Sufis after: lsn=%d newvaValid=%d newva=%d VTS=%d VTA=%d VSNack=%d NackedBlocksWaiting=%d",
|
|
(int)lsn,newvaValid,(int)newva,(int)mVTS,(int)mVTA,(int)mVSNack,mNackedBlocksWaiting);
|
|
return; // SUFI_ACK is always the last field.
|
|
}
|
|
case SUFI_LIST: {
|
|
unsigned numpairs = vec->readField(rp,4);
|
|
if (numpairs == 0) {
|
|
RLCERR("Received SUFI LIST with length==0");
|
|
return; // Give up.
|
|
}
|
|
for (i = 0; i < numpairs; i++) {
|
|
sn = vec->readField(rp,12);
|
|
if (i == 0) { newva = minSN(newva,sn); newvaValid=true; }
|
|
unsigned nackcount = vec->readField(rp,4) + 1;
|
|
for (j = 0; j < nackcount; j++, incSN(sn)) {
|
|
setNAck(sn);
|
|
}
|
|
}
|
|
RLCLOG("received SUFI_LIST n=%d",numpairs);
|
|
continue;
|
|
}
|
|
case SUFI_BITMAP: {
|
|
// There is a note in 9.2.2.11.5:
|
|
// "NOTE: The transmission window is not advanced based on BITMAP SUFIs"
|
|
// So why is this note in the BITMAP sufi and not in the LIST and RLIST sufis, you ask?
|
|
// It is because all acks are negative, and instead of using positive acks,
|
|
// UMTS sends an "ACK" sufi that advances VTA, which implicitly positively
|
|
// acks all passed over blocks. Therefore you are not to construe
|
|
// the bitmap entries with the bit set as a positive ack, you are only
|
|
// to pay attention to the negative acks in the bitmap.
|
|
unsigned maplen = 8*(vec->readField(rp,4) + 1); // Size of bitmap in bits
|
|
sn = vec->readField(rp,12);
|
|
for (i = 0; i < maplen; i++, incSN(sn)) {
|
|
int bit = vec->readField(rp,1);
|
|
if (bit == 0) {
|
|
newva = minSN(newva,sn);
|
|
newvaValid = true;
|
|
setNAck(sn);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
case SUFI_RLIST: { // The inventor of this was on drugs.
|
|
unsigned numcw = vec->readField(rp,4);
|
|
sn = vec->readField(rp,12);
|
|
setNAck(sn);
|
|
newva = minSN(newva,sn);
|
|
newvaValid = true;
|
|
incSN(sn);
|
|
unsigned accumulator = 0;
|
|
bool superSpecialErrorBurstIndicatorFlag = false;
|
|
for (i = 0; i < numcw; i++) {
|
|
unsigned cw = vec->readField(rp,4);
|
|
if (cw == 1) {
|
|
if (accumulator) {
|
|
RLCERR("Received invalid SUFI RLIST with incorrectly placed error burst indicator");
|
|
return; // Give up.
|
|
}
|
|
superSpecialErrorBurstIndicatorFlag = true;
|
|
continue;
|
|
} else {
|
|
accumulator = (accumulator << 3) | (cw>>1);
|
|
if (cw & 1) {
|
|
if (superSpecialErrorBurstIndicatorFlag) {
|
|
while (accumulator--) {
|
|
setNAck(sn);
|
|
incSN(sn);
|
|
}
|
|
} else {
|
|
// Gag me, the spec is not clear what the distance really is:
|
|
// "the number ... represents a distance between the previous indicated
|
|
// erroneous AMD PDU up to and including the next erroneous AMD PDU."
|
|
sn = addSN(sn,accumulator-1);
|
|
setNAck(sn);
|
|
incSN(sn);
|
|
}
|
|
superSpecialErrorBurstIndicatorFlag = false;
|
|
accumulator = 0;
|
|
}
|
|
}
|
|
}
|
|
if (superSpecialErrorBurstIndicatorFlag) {
|
|
// It is an error. We are supposed to go back and undo everything
|
|
// we just did, but heck with that.
|
|
RLCERR("Received invalid SUFI RLIST with trailing error burst indicator");
|
|
continue;
|
|
}
|
|
continue;
|
|
}
|
|
case SUFI_MRW: { // We dont implement this, but parse over it anyway.
|
|
unsigned numMRW = vec->readField(rp,4);
|
|
if (numMRW == 0) numMRW = 1; // special case
|
|
for (i = 0; i < numMRW; i++) {
|
|
vec->readField(rp,12); // MRW
|
|
}
|
|
vec->readField(rp,4); // N_length
|
|
continue;
|
|
}
|
|
case SUFI_MRW_ACK: { // We dont implement, but parse over it anyway.
|
|
/*unsigned numMRWAck =*/ vec->readField(rp,4);
|
|
vec->readField(rp,4); // N
|
|
vec->readField(rp,12); // SN_ACK
|
|
continue;
|
|
}
|
|
case SUFI_POLL: {
|
|
// This can only be used if "flexible RLC PDU size" is configured,
|
|
// and I dont think it will be.
|
|
sn = vec->readField(rp,12);
|
|
// TODO...
|
|
continue;
|
|
}
|
|
default:
|
|
RLCERR("Received invalid SUFI type=%d",sufitype);
|
|
return;
|
|
} // switch
|
|
} // while
|
|
}
|
|
|
|
void URlcTransAm::processSUFIs(ByteVector *vec)
|
|
{
|
|
size_t rp = 4;
|
|
processSUFIs2(vec,rp);
|
|
|
|
if (mConfig->mPoll.mTimerPoll && mTimer_Poll.active()) {
|
|
// Reset Timer_Poll exactly as per 25.322 9.5 paragraph a.
|
|
// The purpose of the poll timer is to positively insure that
|
|
// the poll that occurred at mTimer_Poll_VTS gets through.
|
|
// This complicated check is to prevent an in-flight status report from
|
|
// turning of the Poll timer prematurely. It is over-kill; I think
|
|
// we could just test mNackedBlocksWaiting, which forces a re-poll.
|
|
URlcPdu *pdu=NULL;
|
|
assert(mTimer_Poll_VTS < AmSNS);
|
|
if (deltaSN(mVTA,mTimer_Poll_VTS) >= 0 ||
|
|
((pdu = mPduTxQ[mTimer_Poll_VTS]) && pdu->mNacked)) {
|
|
RLCLOG("Timer_Poll.reset VTA=%d Timer_Poll_VTS=%d nacked=%d",
|
|
(int)mVTA,(int)mTimer_Poll_VTS,pdu?pdu->mNacked:-1);
|
|
mTimer_Poll.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Move mVSNack forward to the next negatively acknowledged block, if any.
|
|
// If none, reset mNackedBlocksWaiting.
|
|
// Apparently we dont resend blocks awaiting an acknack.
|
|
// If fromScratch, start over from the beginning.
|
|
void URlcTransAm::advanceVS(bool fromScratch)
|
|
{
|
|
if (fromScratch) {
|
|
mVSNack = mVTA;
|
|
} else {
|
|
incSN(mVSNack); // Skip nacked block we just sent.
|
|
}
|
|
for ( ; deltaSN(mVSNack,mVTS) < 0; incSN(mVSNack)) {
|
|
URlcPdu *pdu = mPduTxQ[mVSNack];
|
|
if (! pdu) { continue; } // block was acked and deleted.
|
|
if (pdu->mNacked) return;
|
|
}
|
|
// No more negatively acknowledged blocks at the moment.
|
|
// But note there may be lots of blocks that are UnAcked.
|
|
mNackedBlocksWaiting = false;
|
|
}
|
|
|
|
bool URlcTransAm::IsPollTriggered()
|
|
{
|
|
if (mPollTriggered) return true;
|
|
|
|
// 9.7.1 case 4.
|
|
if (mConfig->mPoll.mPollPdu) {
|
|
if (deltaSN(mVTPDU,mVTPDUPollTrigger) >= 0) {
|
|
RLCLOG("PollPdu triggered, VTPDU=%d trig=%d",(int)mVTPDU,(int)mVTPDUPollTrigger);
|
|
mPollTriggered = true;
|
|
mVTPDUPollTrigger = mVTPDU + mConfig->mPoll.mPollPdu;
|
|
}
|
|
}
|
|
|
|
// 9.7.1 case 5.
|
|
// The poll is meant to be sent after the entire SDU has been sent
|
|
// so we get acknowledgement for the whole thing, so if the
|
|
// special case mLILeftOver is still outstanding, wait for that.
|
|
if (mConfig->mPoll.mPollSdu) {
|
|
int diff = deltaSN(mVTSDU,mVTSDUPollTrigger);
|
|
if (diff > 0 || (diff == 0 && mLILeftOver == 0)) {
|
|
RLCLOG("PollSdu triggered VTSDU=%d trig=%d",(int)mVTSDU,(int)mVTSDUPollTrigger);
|
|
mPollTriggered = true;
|
|
mVTSDUPollTrigger = mVTSDU + mConfig->mPoll.mPollSdu;
|
|
}
|
|
}
|
|
|
|
// 9.7.1 case 7.
|
|
if (mConfig->mPoll.mTimerPollPeriodic) {
|
|
if (mTimer_Poll_Periodic.expired()) {
|
|
RLCLOG("TimerPollPeriodic triggered");
|
|
mPollTriggered = true;
|
|
mTimer_Poll_Periodic.set();
|
|
}
|
|
}
|
|
|
|
// 9.7.1 case 3. Described more thoroughly in 9.5
|
|
if (mConfig->mPoll.mTimerPoll) {
|
|
if (mTimer_Poll.expired()) {
|
|
RLCLOG("TimerPoll triggered");
|
|
mPollTriggered = true;
|
|
// This timer is reset when we actually send the poll.
|
|
}
|
|
}
|
|
return mPollTriggered;
|
|
}
|
|
|
|
bool URlcTransAm::stalled()
|
|
{
|
|
// FIXME: This does not work unless mVTWS is less than mSNS/2.
|
|
return deltaSN(mVTS,mVTA) >= mVTWS;
|
|
}
|
|
|
|
URlcPdu *URlcTransAm::readLowSidePdu2()
|
|
{
|
|
if (mRlcState != RLC_RUN) { return NULL; }
|
|
|
|
if (mSendResetAck) {
|
|
mSendResetAck = false;
|
|
return getResetPdu(PDUTYPE_RESET_ACK);
|
|
}
|
|
|
|
// Did we initiate a reset procedure, and are awaiting the Reset_ack?
|
|
if (resetInProgress()) {
|
|
RLCLOG("Reset in progress remaining=%d",(int)mTimer_RST.remaining());
|
|
// Is the timer expired?
|
|
if (mTimer_RST.expired()) {
|
|
// Resend the same reset.
|
|
// Note that the default max_RST for SRBs is just 1,
|
|
// so if a reset is lost, the channel is abandoned.
|
|
mResetTriggered = true; // handled below.
|
|
} else {
|
|
return NULL; // Nothing to send; waiting on reset ack.
|
|
}
|
|
}
|
|
|
|
// If VRH and VRR become inverted, something terrible is wrong.
|
|
// Send a reset. Also see addAckNack().
|
|
if (!receiver()->isReceiverOk()) {
|
|
mResetTriggered = true;
|
|
}
|
|
|
|
// 11.4.2 Reset Initiation
|
|
if (mResetTriggered) {
|
|
RLCLOG("reset triggered VTRST=%d max=%d",(int)mVTRST,mConfig->mMaxRST);
|
|
mResetTriggered = 0;
|
|
// We dont flush mPDUs and discard partial sdus until we get the reset_ack,
|
|
// although I'm not sure why the timing would matter.
|
|
mVTRST++;
|
|
if (mVTRST > mConfig->mMaxRST) {
|
|
RLCERR("too many resets, connection disabled");
|
|
// Too many resets. Give up.
|
|
mTimer_RST.reset(); // Finished with the timer.
|
|
mRlcState = RLC_STOP;
|
|
// TODO: We may want to flush the SDU buffer.
|
|
// TODO: clause 11.4.4a, which is send unrecoverable error to upper layer.
|
|
return NULL;
|
|
}
|
|
mTimer_RST.set(mConfig->mTimerRSTValue);
|
|
// We need to increment RSN between resets, but we cant really do it
|
|
// after we send the RESET_ACK in recvResetAck,
|
|
// because we might get multiple ones of those.
|
|
// It is easier to increment it before starting a new reset procedure.
|
|
return getResetPdu(PDUTYPE_RESET);
|
|
}
|
|
|
|
// Optional here: timer based status transfer; If mTimer_status_periodic expired,
|
|
// set mStatusTriggered.
|
|
|
|
// mStatusTriggered may be triggered when the receiver notices a missing pdu,
|
|
// or when requested by a poll (that indicates PDU was last in senders buffer,
|
|
// or optionally by timer in sender), or optionally by timer in receiver.
|
|
// mStatusTriggered will not be reset until we have transmitted
|
|
// enough pdus for a complete status report.
|
|
if (mStatusTriggered) {
|
|
// We are allowed to piggy-back the status, but not implemented for downlink.
|
|
return getStatusPdu();
|
|
}
|
|
|
|
// Find a pdu to send. The pdu variable may only be a data pdu, because
|
|
// at the end we will set/unset the poll bit, which is only valid in data pdus.
|
|
URlcPdu *pdu = NULL;
|
|
|
|
// Section 11.3.2 Transmission of AMD PDU: this section is confusing.
|
|
// Essentially it is establishing the priority of PDUs to be sent, which is:
|
|
// 1. negatively acknowledged PDUs.
|
|
// 2. new PDUs.
|
|
// 3. I dont see where it says anything about resending pdus before
|
|
// receiving negative ack.
|
|
// If a status report is triggered, it can be sent in a stand-alone status report,
|
|
// or it can be piggy-backed onto a previously sent PDU.
|
|
// If the Configured_Tx_Window_Size >= 2048 (half the sequence space) then
|
|
// you may only resend the most recently sent PDU.
|
|
|
|
if (mNackedBlocksWaiting) {
|
|
// Send this negatively acknowledged pdu.
|
|
// TODO: If we support piggy-backed status, that needs to be fixed here too.
|
|
pdu = mPduTxQ[mVSNack];
|
|
pdu->mNacked = false;
|
|
// Unset the poll bit in case it had been set on the previous transmission.
|
|
pdu->setAmP(false);
|
|
advanceVS(false);
|
|
// 9.7.1 case 2: If the AMD PDU is the last of the AMD PDUs scheduled for retransmission... poll.
|
|
if (!mNackedBlocksWaiting && mConfig->mPoll.mLastRetransmissionPduPoll) {
|
|
mPollTriggered = true;
|
|
}
|
|
} else if (stalled()) {
|
|
RLCLOG("Stalled VTS=%d VTA=%d",(int)mVTS,(int)mVTA);
|
|
// No new data to send, but go to maybe_poll because if the
|
|
// previous poll was prohibited by the mTimerPollProhibit, it may
|
|
// have expired now and we can finally send the poll.
|
|
goto maybe_poll;
|
|
} else if ((pdu = getDataPdu())) {
|
|
// 9.7.1 case 1: If it is the last PDU available... poll.
|
|
if (mConfig->mPoll.mLastTransmissionPduPoll && pdusFinished()) {
|
|
mPollTriggered = true;
|
|
}
|
|
} else {
|
|
// No new pdus to send, but according to 9.7.1, we may need to send
|
|
// a poll anyway so dont return yet.
|
|
}
|
|
|
|
if (pdu && ++pdu->mVTDAT >= mConfig->mMaxDAT) {
|
|
// 25.322 11.3.3a: Reached maximum number of attempts.
|
|
// Note that the documentation of the option names does not exactly match
|
|
// the names in 25.331 RRC 10.3.4.25 Transmission RLC Discard IE.
|
|
RLCLOG("pdu %d exceeded VTDAT=%d, discarded",pdu->getAmSN(),pdu->mVTDAT);
|
|
switch (mConfig->mRlcDiscard.mSduDiscardMode) {
|
|
default:
|
|
RLCERR("Unsupported RLC discard mode configured:%d",
|
|
(int)mConfig->mRlcDiscard.mSduDiscardMode);
|
|
// fall through.
|
|
case TransmissionRlcDiscard::NoDiscard:
|
|
// "No discard" means no explicit discard of just this SDU using MRW sufis;
|
|
// instead we just reset the whole connection.
|
|
mResetTriggered = true;
|
|
return readLowSidePdu2(); // recur to send reset pdu
|
|
}
|
|
}
|
|
|
|
// Set the poll bit if any poll triggers as per 9.7.1, starting at:
|
|
// "When the Polling function is triggered, the Sender shall..."
|
|
maybe_poll:
|
|
if (mConfig->mPoll.mTimerPoll) {
|
|
if (mTimer_Poll.active()) {
|
|
RLCLOG("Timer_poll active=%d remaining=%ld",mTimer_Poll.active(),mTimer_Poll.remaining());
|
|
}
|
|
}
|
|
if (IsPollTriggered()) {
|
|
if (mConfig->mPoll.mTimerPollProhibit && !mTimer_Poll_Prohibit.expired()) {
|
|
// Delay polling.
|
|
} else {
|
|
if (pdu == NULL) {
|
|
// 9.7.1, near top of page 54:
|
|
// "If there is one or more AMD PDUs to be transmitted or
|
|
// there are AMD PDUs not acknowledged by the Receiver:"
|
|
// Note carefully: It does not say "negatively acknowledged" blocks,
|
|
// which would be a test of mNackedBlocksWaiting; rather we resend
|
|
// the poll request if any blocks have not been acknowledged,
|
|
// which occurs if mVTS > mVTA.
|
|
if (mVTS != mVTA) {
|
|
// We need to resend a PDU just to set the poll bit.
|
|
// This is particularly important for the Timer_Poll.
|
|
// Consider what happens if the PDU carrying the poll bit is lost;
|
|
// then the RLC on the other end does not respond, the
|
|
// Timer_Poll expires, and we get to here now.
|
|
// 11.3.2 says if we need to set a poll and have nothing
|
|
// to send, resent pdu[mVTS-1]
|
|
URlcSN psn = addSN(mVTS,-1);
|
|
pdu = mPduTxQ[psn];
|
|
if (pdu == NULL) {
|
|
RLCERR("internal error: pdu[mVTS-1] is missing");
|
|
return NULL;
|
|
}
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// Send the poll.
|
|
mPollTriggered = false;
|
|
pdu->setAmP(true);
|
|
RLCLOG("Poll Requested at sn=%d",pdu->getSN());
|
|
if (mConfig->mPoll.mTimerPollProhibit) { mTimer_Poll_Prohibit.set(); }
|
|
if (mConfig->mPoll.mTimerPoll) {
|
|
mTimer_Poll.set();
|
|
mTimer_Poll_VTS = mVTS;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If sending a data pdu, it is saved in mPduTxQ, so we have to send a copy
|
|
// for the caller to delete.
|
|
if (pdu) {
|
|
pdu = new URlcPdu(pdu);
|
|
RN_MEMLOG(URlcPdu,pdu);
|
|
}
|
|
|
|
return pdu;
|
|
}
|
|
|
|
URlcPdu *URlcTransAm::readLowSidePdu()
|
|
{
|
|
ScopedLock lock(parent()->mAmLock);
|
|
URlcPdu *pdu = readLowSidePdu2();
|
|
if (pdu) {
|
|
bool dc = pdu->getBit(0);
|
|
if (dc) {
|
|
// Data pdu:
|
|
RLCLOG("readLowSidePdu dc=data sn=%d poll=%d header=%s",
|
|
(int)pdu->getField(1,12),
|
|
(int)pdu->getField(URlcPdu::sPollBit,1),
|
|
pdu->segment(0,2).hexstr().c_str());
|
|
} else {
|
|
// Control pdu: the sn is not applicable.
|
|
RLCLOG("readLowSidePdu dc=control header=%s", pdu->segment(0,2).hexstr().c_str())
|
|
}
|
|
}
|
|
return pdu;
|
|
}
|
|
|
|
void URlcRecvAmUm::addUpSdu(ByteVector &payload)
|
|
{
|
|
if (mUpSdu == NULL) {
|
|
mUpSdu = new URlcUpSdu(mConfig->mMaxSduSize);
|
|
RN_MEMLOG(URlcUpSdu,mUpSdu);
|
|
mUpSdu->setAppendP(0); // Allow appending
|
|
}
|
|
mUpSdu->append(payload);
|
|
}
|
|
|
|
// A gag me special case for LI == 0x7ffc buried in sec 9.2.2.8
|
|
void URlcRecvAmUm::ChopOneByteOffSdu(ByteVector &payload)
|
|
{
|
|
if (mUpSdu == NULL || mUpSdu->size() < 1) {
|
|
RLCERR("Logic error in the horrible LI=0x7ffc special case");
|
|
return; // and we are done with that, I guess
|
|
}
|
|
mUpSdu->trimRight(1); // Chop off the last byte.
|
|
}
|
|
|
|
void URlcRecvAmUm::sendSdu()
|
|
{
|
|
rlcSendHighSide(mUpSdu);
|
|
mUpSdu = NULL;
|
|
}
|
|
|
|
void URlcRecvAmUm::discardPartialSdu()
|
|
{
|
|
if (mUpSdu) {
|
|
RLCLOG("discardPartialSdu");
|
|
delete mUpSdu;
|
|
mUpSdu = 0;
|
|
// todo: alert other layers.
|
|
}
|
|
}
|
|
|
|
// It cant be const.
|
|
void URlcTrans::textTrans(std::ostream &os)
|
|
{
|
|
ScopedLock lock(mQLock); // We are touching mSplitSdu
|
|
os <<LOGVAR(mVTSDU);
|
|
os <<LOGVAR2("mSplitSdu.size",(mSplitSdu ? mSplitSdu->size() : 0));
|
|
os <<LOGVAR2("getSduCnt", getSduCnt());
|
|
os <<LOGVAR2("rlcGetSduQBytesAvail",rlcGetSduQBytesAvail());
|
|
os <<LOGVAR2("rlcGetPduCnt",rlcGetPduCnt());
|
|
os <<LOGVAR2("rlcGetFirstPduSizeBits",rlcGetFirstPduSizeBits());
|
|
os <<LOGVAR2("rlcGetDlPduSizeBytes",rlcGetDlPduSizeBytes());
|
|
}
|
|
|
|
void URlcTransAmUm::textAmUm(std::ostream &os)
|
|
{
|
|
os <<LOGVAR2("mDlPduSizeBytes",mConfig->mDlPduSizeBytes)
|
|
<<LOGVAR2("PduOutQ.size",mPduOutQ.size())
|
|
<<LOGVAR(mVTPDU)<<LOGVAR(mLILeftOver)
|
|
<<LOGVAR2("rlcGetBytesAvail",rlcGetBytesAvail())
|
|
<<LOGVAR2("rlcGetPduCnt",rlcGetPduCnt());
|
|
}
|
|
|
|
void URlcTransAm::transAmReset()
|
|
{
|
|
RLCLOG("transAmReset");
|
|
URlcTransAmUm::transDoReset();
|
|
mVTS = 0;
|
|
mVTA = 0;
|
|
mVTWS = mConfig->mConfigured_Tx_Window_Size;
|
|
mVTPDUPollTrigger = mConfig->mPoll.mPollPdu;
|
|
mVTSDUPollTrigger = mConfig->mPoll.mPollSdu;
|
|
mPollTriggered = mStatusTriggered = mResetTriggered = false;
|
|
mNackedBlocksWaiting = false;
|
|
mVSNack = 0;
|
|
mSendResetAck = false;
|
|
for (int i = 0; i < AmSNS; i++) {
|
|
if (mPduTxQ[i]) { delete mPduTxQ[i]; mPduTxQ[i] = 0; }
|
|
}
|
|
// mResetTransRSN is explicitly not reset.
|
|
// mVTRST is explicitly not reset.
|
|
// mVTMRW = 0; currently unused
|
|
|
|
//if (mConfig->mTimerRSTValue) mTimer_RST.reset(); // <- done by caller:
|
|
// 25.322 11.4.1: Reset does not reset mTimerPollPeriodic
|
|
//if (mConfig->mPoll.mTimerPollPeriodic) mTimer_Poll_Periodic.reset();
|
|
if (mConfig->mPoll.mTimerPollProhibit) mTimer_Poll_Prohibit.reset();
|
|
if (mConfig->mPoll.mTimerPoll) mTimer_Poll.reset();
|
|
}
|
|
|
|
void URlcTransAm::text(std::ostream&os)
|
|
{
|
|
URlcTrans::textTrans(os);
|
|
URlcTransAmUm::textAmUm(os);
|
|
|
|
os <<LOGVAR(mVTS) <<LOGVAR(mVTA) <<LOGVAR(mVTWS)
|
|
<<LOGVAR(mPollTriggered) <<LOGVAR(mStatusTriggered) <<LOGVAR(mResetTriggered)
|
|
<<LOGVAR(mNackedBlocksWaiting) <<LOGVAR(mSendResetAck)
|
|
<<LOGVAR(mResetTransRSN) <<LOGVAR(mResetAckRSN) <<LOGVAR(mResetRecvCount);
|
|
int cnt = 0;
|
|
for (int sns = 0; sns < AmSNS; sns++) {
|
|
if (mPduTxQ[sns]) {
|
|
if (0==cnt++) os <<"\nPduTxQ=";
|
|
os <<"\t"<<LOGVAR(sns); mPduTxQ[sns]->text(os); os<<"\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
void URlcTransUm::text(std::ostream&os)
|
|
{
|
|
URlcTrans::textTrans(os);
|
|
URlcTransAmUm::textAmUm(os);
|
|
os <<LOGVAR(mVTUS);
|
|
}
|
|
|
|
void URlcTransAmUm::transDoReset()
|
|
{
|
|
mLILeftOver = 0;
|
|
mVTPDU = 0;
|
|
mVTSDU = 0;
|
|
ScopedLock lock(mQLock); // We are touching mSplitSdu
|
|
if (mSplitSdu) {
|
|
// First sdu was partially sent; throw it away.
|
|
mSplitSdu->free();
|
|
mSplitSdu = NULL;
|
|
}
|
|
}
|
|
|
|
void URlcTransAm::transAmInit()
|
|
{
|
|
mResetTransRSN = 0;
|
|
mResetAckRSN = 0;
|
|
mResetRecvCount = 0;
|
|
memset(mPduTxQ,0,sizeof(mPduTxQ));
|
|
mTimer_RST.configure(mConfig->mTimerRSTValue);
|
|
mTimer_Poll_Periodic.configure(mConfig->mPoll.mTimerPollPeriodic);
|
|
mTimer_Poll_Prohibit.configure(mConfig->mPoll.mTimerPollProhibit);
|
|
mTimer_Poll.configure(mConfig->mPoll.mTimerPoll);
|
|
// etc...
|
|
transAmReset();
|
|
}
|
|
|
|
// Set the nack indicator for queued block with this sequence number.
|
|
void URlcTransAm::setNAck(URlcSN sn)
|
|
{
|
|
assert(sn >= 0 && sn < AmSNS);
|
|
if (URlcPdu *pdu = mPduTxQ[sn]) {
|
|
pdu->mNacked = true;
|
|
mNackedBlocksWaiting = true;
|
|
RLCLOG("setNack %d pdu->sn=%d",(int)sn,pdu->getAmSN());
|
|
} else {
|
|
RLCLOG("setNack %d MISSING PDU!",(int)sn);
|
|
}
|
|
}
|
|
|
|
|
|
// reset procedure goes both ways:
|
|
// 1. we send reset, finish reset upon receipt of reset_ack.
|
|
// In this case, continue to send reset until we get reset_ack.
|
|
// If we send MaxRST attempts, unrecoverable error.
|
|
// 11.4.5.3: We may receive a reset after we sent one, with a different RSN
|
|
// than the one we sent, in which case send an ack using the received RSN.
|
|
// This implies that there are two RSN variables: one of the Reset we sent
|
|
// so we can resend the same value on timer expirey, and one of the incoming Reset.
|
|
// 2. we get reset, do the reset and send reset_ack.
|
|
// In this case, continue to respond to reset by sending reset_ack as
|
|
// long as incoming reset matches most recent.
|
|
// The reset procedure is really queer. The receiver of the RESET only
|
|
// does the full reset the first time it is received. It then returns
|
|
// a RESET_ACK and can immediately begin blasting away with more pdus
|
|
// oblivious to whether the RESET_ACK was received or not.
|
|
// If the sender does not receive the first RESET_ACK, it times out and sends
|
|
// another RESET which does NOT reset the receiver; the receiver merely sends
|
|
// another RESET_ACK. When the RESET_ACK finally gets through to the sender,
|
|
// it discards everything that has been sent in the interim, so the first order
|
|
// of business is to send status reports back to the receiver so it can
|
|
// resent everything again.
|
|
// 11.4.3 or 11.4.5.3, which are handled identically except that case 11.4.3 ignores
|
|
// duplicate resets.
|
|
void URlcAm::recvResetPdu(URlcPdu *pdu)
|
|
{
|
|
unsigned rsn = pdu->getBit(4);
|
|
unsigned hfn = pdu->getField(8,20);
|
|
RLCLOG("receive reset pdu cnt=%d rsn=%d ResetAckRSN=%d",mResetRecvCount,rsn,mResetAckRSN);
|
|
// If resetInProgress, we already sent a reset, and now we have received one back,
|
|
// which corresponds to clause 11.4.5.3. Do the reset now.
|
|
if (resetInProgress() || mResetRecvCount == 0 || rsn != mResetAckRSN) {
|
|
// This is a new reset.
|
|
RLCLOG("full reset");
|
|
transAmReset();
|
|
recvAmReset();
|
|
} else {
|
|
// Redundant reset message received.
|
|
// Just resend another reset ack and we are done.
|
|
}
|
|
mULHFN = hfn;
|
|
mResetAckRSN = rsn; // RSN in outgoing reset ack must match most recent incoming reset.
|
|
mResetRecvCount++; // Counts all resets received ever, unlike mVTRST counts resents of this reset
|
|
mSendResetAck = true;
|
|
}
|
|
|
|
// 11.4.4
|
|
void URlcAm::recvResetAck(URlcPdu *pdu)
|
|
{
|
|
unsigned rsn = pdu->getBit(4);
|
|
RLCLOG("receive reset_ack pdu rsn=%d ResetTransRSN=%d",rsn,mResetTransRSN);
|
|
// unused unsigned hfn = pdu->getField(8,20);
|
|
if (! resetInProgress()) {
|
|
// This is nothing to worry about because they can pass each other in flight.
|
|
LOG(INFO) <<"Discarding Reset_Ack pdu that does not correspond to a Reset";
|
|
return;
|
|
}
|
|
if (rsn != mResetTransRSN) {
|
|
// This is slightly disturbing, but we'll press on.
|
|
LOG(INFO) <<"Discarding Reset_Ack pdu with invalid rsn";
|
|
return;
|
|
}
|
|
transAmReset();
|
|
recvAmReset();
|
|
mVTRST = 0;
|
|
mTimer_RST.reset();
|
|
mResetTransRSN++;
|
|
mULHFN++;
|
|
mDLHFN++;
|
|
}
|
|
|
|
void URlcRecvAm::recvAmReset()
|
|
{
|
|
URlcRecvAmUm::recvDoReset();
|
|
mVRR = 0;
|
|
mVRH = 0;
|
|
mStatusSN = 0;
|
|
for (int i = 0; i < AmSNS; i++) {
|
|
if (mPduRxQ[i]) { delete mPduRxQ[i]; mPduRxQ[i] = 0; }
|
|
}
|
|
}
|
|
|
|
void URlcRecvUm::text(std::ostream &os)
|
|
{
|
|
os <<LOGVAR(mVRUS);
|
|
#if RLC_OUT_OF_SEQ_OPTIONS
|
|
os <<LOGVAR(VRUDR) <<LOGVAR(VRUDH) <<LOGVAR(VRUDT) <<LOGVAR(VRUOH)
|
|
<<LOGVAR2("VRUM",VRUM());
|
|
#endif
|
|
}
|
|
|
|
void URlcRecvAmUm::textAmUm(std::ostream &os)
|
|
{
|
|
os <<LOGVAR2("mUpSdu.size",(mUpSdu ? mUpSdu->size() : 0));
|
|
os <<LOGVAR(mLostPdu); // This is UM only, but easier to put in this class.
|
|
}
|
|
|
|
void URlcRecvAm::text(std::ostream &os)
|
|
{
|
|
// There is nothing interesting in URlcRecv to warrant a textRecv() function.
|
|
URlcRecvAmUm::textAmUm(os);
|
|
|
|
os <<LOGVAR(mVRR) <<LOGVAR(mVRH) <<LOGVAR2("VRMR",VRMR());
|
|
int cnt=0;
|
|
for (int sns = 0; sns < AmSNS; sns++) {
|
|
if (mPduRxQ[sns]) {
|
|
if (0==cnt++) os <<"\nPduRxQ=";
|
|
os <<"\t"<<LOGVAR(sns); mPduRxQ[sns]->text(os); os <<"\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
void URlcRecvAm::recvAmInit()
|
|
{ // Happens once.
|
|
memset(mPduRxQ,0,sizeof(mPduRxQ));
|
|
recvAmReset();
|
|
}
|
|
|
|
void URlcRecvAmUm::parsePduData(URlcPdu &pdu,
|
|
int headersize, // 1 for UM, 2 for AM
|
|
bool Eindicator, // Was the E-bit in the header?
|
|
bool statusOnly) // If true, process only the piggy-back status, do nothing else.
|
|
{
|
|
ByteVector payload = pdu.tail(headersize);
|
|
if (0 == Eindicator) {
|
|
if (statusOnly) return;
|
|
RLCLOG("parsePduData sn=%d Eindicator=0", pdu.getSN());
|
|
// No LI indicators. Whole payload is appended to current SDU.
|
|
// mLostPdu is only set in UM mode.
|
|
if (! mLostPdu) {
|
|
addUpSdu(payload);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Crack out the length indicators.
|
|
unsigned licnt = 0;
|
|
unsigned vli[32+1];
|
|
unsigned libytes = mConfig->mUlLiSizeBytes;
|
|
unsigned libits = mConfig->mUlLiSizeBits;
|
|
bool end = 0;
|
|
bool overflow = 0;
|
|
while (!end) {
|
|
if (licnt == 32) {
|
|
overflow = true;
|
|
if (payload.size() < libytes) {
|
|
// Block is complete trash.
|
|
RLCERR("UMTS RLC: Invalid incoming PDU");
|
|
if (! statusOnly) discardPartialSdu();
|
|
return;
|
|
}
|
|
} else {
|
|
vli[licnt++] = payload.getField(0,libits);
|
|
}
|
|
end = (0 == payload.getBit(libits)); // e bit.
|
|
payload.trimLeft(libytes);
|
|
RLCLOG("parsePduData sn=%d li=%d",
|
|
pdu.getSN(),vli[licnt-1]); // Note: li==127 means padding.
|
|
}
|
|
|
|
if (statusOnly) {
|
|
// See if the last li indicates a piggy-backed status is present.
|
|
unsigned lastli = vli[licnt-1];
|
|
if (!(lastli == 0x7ffe || (libytes == 1 && lastli == 0x7e))) {
|
|
return; // There is no piggy-backed status present.
|
|
}
|
|
// keep going and we will handle the piggy back status at the bottom.
|
|
}
|
|
|
|
if (overflow) {
|
|
RLCERR("More than 32 segments in an incoming PDU");
|
|
}
|
|
|
|
// Use the length indicators to slice up the payload into segments.
|
|
bool start_sdu = false; // first data byte in pdu starts a new sdu.
|
|
unsigned n = 0; // index into li fields.
|
|
|
|
// Section 11.2.3.1, RLC-UM LI indicators in downlink:
|
|
// first li= 0x7c or 0x7ffc - start of RLC SDU
|
|
// first li= 0x7d or 0x7ffd - complete SDU
|
|
// first li= 0x7ffa - complete SDU - 1 byte
|
|
|
|
// Section 9.2.2.8 RLC-UM LI indicators in uplink:
|
|
// 0x7c or 0x7ffc start of RLC SDU.
|
|
// Section 9.2.2.8 RLC-UM LI indicators in downlink:
|
|
// 0x00 or 0x0000 - start of RLC SDU, if no other indication
|
|
// 0x7ffb - prevous pdu was end of RLC SDU - 1 byte
|
|
|
|
// Alternative E-bit values:
|
|
// first lie=0x7e or 0x7ffe, 0x7d or 0x7ffd, 0x7ffa
|
|
|
|
// 9.2.2.8 Special cases for first length indicator:
|
|
if (vli[0] == 0) {
|
|
// "The previous RLC PDU was exactly filled..."
|
|
start_sdu = true;
|
|
n++; // And we are finished with this LI field - it didnt actually tell us a length.
|
|
} else if (vli[0] == 0x7ffa) { // This is only used with the alternative-E-bit config,
|
|
// so we should probably throw an error.
|
|
start_sdu = true;
|
|
// Dont increment n - instead modify vli with the correct length:
|
|
vli[0] = payload.size() - 1;
|
|
payload.trimRight(1);
|
|
} else if (vli[0] == 0x7ffb) {
|
|
// "The previous RLC PDU was one octet short of exactly filling the previous sdu"
|
|
start_sdu = true;
|
|
n++;
|
|
// This horrible special case instructs to chop one byte off the PREVIOUS pdu. Gag me.
|
|
if (!statusOnly) ChopOneByteOffSdu(payload);
|
|
} else if (vli[0] == 0x7ffc || (libytes == 1 && vli[0] == 0x7c)) {
|
|
// "UMD PDU: The first data octet of this RLC PDU is the first octet
|
|
// of an RLC SDU. AMD PDU: Reserved."
|
|
start_sdu = true;
|
|
n++; // And we are finished with this LI field - it didnt actually tell us a length.
|
|
} else if (vli[0] == 0x7ffd || (libytes == 1 && vli[0] == 0x7d)) {
|
|
// "UMD PDU: The first data octet in this RLC PDU is the first octet of an
|
|
// RLC SDU and the last octet in this RLC PDU is the last octet of the same
|
|
// RLC SDU. AMD PDU: Reserved."
|
|
start_sdu = true;
|
|
vli[0] = payload.size();
|
|
if (licnt != 1) {
|
|
RLCERR("Incoming PDU invalid: LI==0x7ffd but more than one LI");
|
|
}
|
|
} else {
|
|
// If the alternative-E bit, then 0x7ffe and 0x7e have a special meaning
|
|
// in the first position, but we dont use it.
|
|
}
|
|
|
|
// Process the piggy-back status and return.
|
|
if (statusOnly) {
|
|
// Scan past all the other li fields to get to the final one.
|
|
for ( ; n < licnt; n++) {
|
|
unsigned lenbytes = vli[n];
|
|
if (lenbytes == 0x7ffe || (libytes == 1 && lenbytes == 0x7e)) {
|
|
// Finally, here it is.
|
|
if (n+1 != licnt) {
|
|
RLCERR("Incoming piggy-back status indication before end of LI fields");
|
|
return;
|
|
}
|
|
URlcRecvAm *recv = dynamic_cast<URlcRecvAm*>(this);
|
|
if (recv) {
|
|
recv->transmitter()->processSUFIs(&payload);
|
|
} else {
|
|
// The other possibility is that the this object is URlcRecvUm.
|
|
RLCERR("invalid li=0x7fffe or 0x7e in UM mode");
|
|
}
|
|
return;
|
|
}
|
|
if (lenbytes > payload.size()) {
|
|
RLCERR("Incoming piggy-back status LI size=%d less than PDU length=%d",
|
|
lenbytes,payload.size());
|
|
return;
|
|
}
|
|
payload.trimLeft(lenbytes);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (start_sdu) {
|
|
if (mLostPdu) { assert(mUpSdu == NULL); } // this case handled earlier.
|
|
mLostPdu = false;
|
|
// It is an error if mUpSdu is not set, because the sender gave us an LI
|
|
// field that implied that there is an mUpSdu. But lets not crash...
|
|
if (mUpSdu) sendSdu();
|
|
}
|
|
|
|
for ( ; n < licnt; n++) {
|
|
unsigned lenbytes = vli[n];
|
|
//printf("HERE: n=%d libytes=%d lenbytes=%d\n",n,libytes,lenbytes);
|
|
if (lenbytes == 0x7fff || (libytes == 1 && lenbytes == 0x7f) ||
|
|
lenbytes == 0x7ffe || (libytes == 1 && lenbytes == 0x7e)) {
|
|
//printf("THERE: n=%d libytes=%d lenbytes=%d\n",n,libytes,lenbytes);
|
|
// Rest of pdu is padding or a piggy-backed status, which was handled elsewhere.
|
|
payload.trimLeft(payload.size()); // Discard rest of payload.
|
|
if (n+1 != licnt) {
|
|
RLCERR("Incoming PDU padding indication before end of LI fields");
|
|
}
|
|
break;
|
|
}
|
|
|
|
// sanity check.
|
|
if (lenbytes > payload.size()) {
|
|
RLCERR("Incoming PDU LI size=%d less than PDU length=%d", lenbytes,payload.size());
|
|
n = licnt; // End loop after this iteration.
|
|
lenbytes = payload.size(); // Should probably discard this.
|
|
}
|
|
if (!mLostPdu) {
|
|
ByteVector foo(payload.segment(0,lenbytes)); // C++ needs temp variable, sigh.
|
|
addUpSdu(foo);
|
|
sendSdu();
|
|
}
|
|
mLostPdu = false;
|
|
payload.trimLeft(lenbytes);
|
|
}
|
|
if (payload.size()) {
|
|
addUpSdu(payload); // The left-over is the start of a new sdu to be continued.
|
|
}
|
|
}
|
|
|
|
|
|
// TODO: Should we lock this? In case the MAC manages to send a second
|
|
// while we are still doing the first? Probably so.
|
|
void URlcRecvAm::rlcWriteLowSide(const BitVector &pdubits)
|
|
{
|
|
ScopedLock lock(parent()->mAmLock);
|
|
int dc = pdubits.peekField(0,1);
|
|
if (dc == 0) { // is it a control pdu?
|
|
URlcPdu pdu1(pdubits,this,"ul am control");
|
|
PduType pdutype = PduType(pdubits.peekField(1,3));
|
|
|
|
std::ostringstream foo;
|
|
pdubits.hex(foo);
|
|
RLCLOG("rlcWriteLowSide(control,sizebits=%d,pdutype=%d,payload=%s) mVRR=%d",
|
|
pdubits.size(),pdutype,foo.str().c_str(),(int)mVRR);
|
|
|
|
switch (pdutype) {
|
|
case PDUTYPE_STATUS: {
|
|
transmitter()->processSUFIs(&pdu1);
|
|
break;
|
|
}
|
|
case PDUTYPE_RESET: {
|
|
parent()->recvResetPdu(&pdu1);
|
|
break;
|
|
}
|
|
case PDUTYPE_RESET_ACK: { // 11.4.4 Reception of RESET ACK PDU
|
|
parent()->recvResetAck(&pdu1);
|
|
break;
|
|
}
|
|
default:
|
|
RLCERR("RLC received control block with unknown RLC type=%d",pdutype);
|
|
break;
|
|
}
|
|
//delete pdu1; // formerly I allocated the pdu1, but now not needed.
|
|
return;
|
|
} else {
|
|
// It is a data pdu.
|
|
// Check for poll bit:
|
|
if (pdubits.peekField(URlcPdu::sPollBit,1)) {
|
|
RLCLOG("Received poll bit at SN %d",(int)pdubits.peekField(1,12));
|
|
transmitter()->mStatusTriggered = true;
|
|
mStatusSN = mVRR; // This is where we will start the status reports.
|
|
}
|
|
|
|
// If we already have a copy we can discard it before we go to the
|
|
// effort of converting it to a ByteVector.
|
|
URlcSN sn = pdubits.peekField(1,12);
|
|
|
|
if (mUep->mStateChange) {
|
|
int beforesn = sn, beforevrr = mVRR;
|
|
if (sn==0 && (mVRR!=0)) {mUep->reestablishRlcs();}
|
|
LOG(ALERT) << format("stateChange: before %d %d after %d %d",beforesn,beforevrr,(int)sn,(int)mVRR);
|
|
}
|
|
mUep->mStateChange = false;
|
|
|
|
std::ostringstream foo;
|
|
pdubits.hex(foo);
|
|
RLCLOG("rlcWriteLowSide(data,sizebits=%d,sn=%d,payload=%s) mVRR=%d",
|
|
pdubits.size(),(int)sn,foo.str().c_str(),(int)mVRR);
|
|
|
|
if (deltaSN(sn,mVRR) < 0) {
|
|
// The other transmitter sent us a block we have already processed
|
|
// and is no longer in our reception window.
|
|
// This is a common occurrence when there is alot of transmission loss
|
|
// because the other transmitter did not receive our status report
|
|
// informing them that we no longer want this block.
|
|
RLCLOG("rlcWriteLowSide ignoring block sn=%d less than VRR=%d", (int)sn, (int)mVRR);
|
|
// retransmit status message, otherwise this will go on indefinitely, especially on RACH
|
|
transmitter()->mStatusTriggered = true;
|
|
return;
|
|
}
|
|
|
|
URlcPdu *pdu2 = new URlcPdu(pdubits,parent(),"ul am data");
|
|
RN_MEMLOG(URlcPdu,pdu2);
|
|
|
|
// Process piggy-backed status immediately.
|
|
parsePduData(*pdu2,2,pdu2->getAmHE() & 1,true);
|
|
|
|
if (mPduRxQ[sn]) {
|
|
// Already received this block.
|
|
// This is a common occurrence if there are many lost pdus
|
|
// because if the status response is lost, the sender will
|
|
// resend a block just to get a poll across.
|
|
// If we were less lazy, we could check that the two blocks match.
|
|
// If the designers had been more clever, they could have used the two blocks
|
|
// to decode the data more securely.
|
|
delete pdu2;
|
|
RLCLOG("rlcWriteLowSide ignoring duplicate block sn=%d", (int)sn);
|
|
// retransmit status message, otherwise this will go on indefinitely, especially on RACH
|
|
transmitter()->mStatusTriggered = true;
|
|
return;
|
|
}
|
|
|
|
mPduRxQ[sn] = pdu2;
|
|
|
|
if (deltaSN(sn,mVRH) >= 0) {
|
|
if (mConfig->mStatusDetectionOfMissingPDU) {
|
|
if (sn != addSN(mVRH,1)) {
|
|
// We skipped a pdu. Trigger a status report to inform the other.
|
|
transmitter()->mStatusTriggered = true;
|
|
}
|
|
}
|
|
mVRH = sn; incSN(mVRH);
|
|
}
|
|
|
|
// Now parse any new blocks received. This is advanceVRR();
|
|
if (sn == mVRR) {
|
|
// Woo hoo! This is the block we have been waiting for!
|
|
// Advance mVRR over all consecutive blocks that have been received.
|
|
URlcPdu *pdu3;
|
|
while ((pdu3 = mPduRxQ[mVRR])) {
|
|
parsePduData(*pdu3,2,pdu3->getAmHE() & 1,false);
|
|
delete pdu3;
|
|
mPduRxQ[mVRR] = 0;
|
|
incSN(mVRR);
|
|
}
|
|
} else {
|
|
// It is not possible for block mVRR to exist yet.
|
|
assert(mPduRxQ[mVRR] == NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Out of Sequence SDU Delivery not implemented.
|
|
// We would only need this if we used multiple physical channels, and we wont.
|
|
void URlcRecvUm::rlcWriteLowSide(const BitVector &pdubits)
|
|
{
|
|
URlcPdu pdu(pdubits,this,"ul um");
|
|
|
|
URlcSN sn = pdu.getSN();
|
|
#if RLC_OUT_OF_SEQ_OPTIONS // not fully implemented
|
|
if (mConfig->mmConfigOSR) {
|
|
// 11.2.3.1 SDU discard and re-assembly
|
|
if (deltaSN(sn,VRUM()) >= 0) { /*delete pdu;*/ return; }
|
|
}
|
|
#endif
|
|
//cout << "sn: " << sn << ", mVRUS: " << mVRUS << endl;
|
|
if (sn != mVRUS) {
|
|
// Lost one or more pdus.
|
|
// Discard any partially assembled SDU.
|
|
discardPartialSdu();
|
|
// Set mLostPdu to continue to discard data until we find a certain start of a new sdu.
|
|
mLostPdu = true;
|
|
}
|
|
mVRUS = addSN(sn,1);
|
|
|
|
// Note: payload does not 'own' memory; must delete original pdu when finished.
|
|
parsePduData(pdu, 1, pdu.getUmE(),false);
|
|
// Automatic deletion of ByteVector in pdu.
|
|
}
|
|
|
|
// This is applicable only to RLC-UM and RLC-AM. Return 0 for RLC-TM.
|
|
unsigned computeDlRlcSize(RBInfo *rb, RrcTfs *dltfs)
|
|
{
|
|
if (rb->getDlRlcMode() == URlcModeTm) {return 0;}
|
|
TrChInfo *dltc = dltfs->getTrCh();
|
|
unsigned dlmacbits = macHeaderSize(dltc->mTransportChType,rb->mRbId,dltc->mTcIsMultiplexed);
|
|
|
|
// For UM and AM there should only be one TB size, although there is
|
|
// sometimes a 0 size as well, which is why we use the MaxTBSize.
|
|
// For TM there could be multiple TB sizes, but we dont care because
|
|
// they just pass through for the MAC to worry about.
|
|
// UM and AM RLC are byte-oriented, so ??
|
|
return (dltfs->getMaxTBSize() - dlmacbits)/8;
|
|
}
|
|
|
|
|
|
// Allocate the uplink and downlink RLC entities for this rb.
|
|
URlcPair::URlcPair(RBInfo *rb, RrcTfs *dltfs, UEInfo *uep, TrChId tcid)
|
|
: mTcid(tcid)
|
|
{
|
|
// We need the mac header size for this particular rb to subtract from the TrCh TBSize.
|
|
// We pass that size to the AM/UM mode configs, and they subtract the RLC AM or UM
|
|
// header size out of this pdusize.
|
|
unsigned dlPduSizeBytes = computeDlRlcSize(rb,dltfs);
|
|
|
|
{
|
|
TrChInfo *dltc = dltfs->getTrCh();
|
|
unsigned dlmacbits = macHeaderSize(dltc->mTransportChType,rb->mRbId,dltc->mTcIsMultiplexed);
|
|
PATLOG(4,format("URlcPair(rb=%d,ul=%s,dl=%s,pdusizebits=%d+%d,%s)",
|
|
rb->mRbId, URlcMode2Name(rb->getUlRlcMode()), URlcMode2Name(rb->getDlRlcMode()),
|
|
dlmacbits,dlPduSizeBytes, uep->ueid().c_str()));
|
|
}
|
|
|
|
// We do not need to pass a pdu size to the uplink RLC entities because they just
|
|
// assemble whatever size pdus come in.
|
|
switch (rb->getUlRlcMode()) {
|
|
case URlcModeAm: { // If ul is AM, dl is AM too.
|
|
assert(rb->getDlRlcMode() == URlcModeAm);
|
|
URlcAm *amrlc = new URlcAm(rb,dltfs,uep,dlPduSizeBytes); // Includes UrlcTransAm and UrlcRecvAm
|
|
mDown = amrlc;
|
|
mUp = amrlc;
|
|
return;
|
|
}
|
|
case URlcModeUm:
|
|
mUp = new URlcRecvUm(rb,dltfs,uep);
|
|
break;
|
|
case URlcModeTm:
|
|
// TODO: Add config to TM?
|
|
mUp = new URlcRecvTm(rb,uep);
|
|
break;
|
|
}
|
|
switch (rb->getDlRlcMode()) {
|
|
case URlcModeAm: assert(0); // handled above.
|
|
break;
|
|
case URlcModeUm:
|
|
mDown = new URlcTransUm(rb,dltfs,uep,dlPduSizeBytes);
|
|
break;
|
|
case URlcModeTm:
|
|
mDown = new URlcTransTm(rb,uep);
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
URlcPair::~URlcPair()
|
|
{
|
|
if (mDown->mRlcMode == URlcModeAm) {
|
|
// It is RLC-AM, and there is only one combined entity.
|
|
assert(mUp->mRlcMode == URlcModeAm);
|
|
URlcAm *am = dynamic_cast<URlcAm*>(mDown);
|
|
assert(am);
|
|
delete am;
|
|
} else {
|
|
delete mUp;
|
|
delete mDown;
|
|
}
|
|
}
|
|
|
|
}; // namespace UMTS
|