/* * Copyright 2011, 2014 Range Networks, Inc. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ #define LLC_IMPLEMENTATION 1 #include "GPRSL3Messages.h" #include "Sgsn.h" #include "Ggsn.h" #include "LLC.h" #define CASENAME(x) case x: return #x; namespace SGSN { const char *LlcSapi::name(type sapi) { switch (sapi) { CASENAME(GPRSMM) CASENAME(TOM2) CASENAME(UserData3) CASENAME(UserData5) CASENAME(SMS) CASENAME(TOM8) CASENAME(UserData9) CASENAME(UserData11) default: return "unrecognized LlcSapi"; } } const char *LLCFormat::name(type format) { switch (format) { CASENAME(Invalid) CASENAME(I) CASENAME(S) CASENAME(UI) CASENAME(U) CASENAME(ISack) CASENAME(SSack) default: return "unrecognized LLCFormat"; } } LLCFormat::type LlcFrame::getFormat() { if (size() < 2) { return LLCFormat::Invalid; } unsigned tag = getControl(0); if ((tag & 0x80) == 0) { return (getControl(2) & 3) == 3 ? LLCFormat::ISack : LLCFormat::I; } else if ((tag & 0x40) == 0) { return (getControl(1) & 3) == 3 ? LLCFormat::SSack : LLCFormat::S; } else if ((tag & 0x20) == 0) { return LLCFormat::UI; } else { return LLCFormat::U; } } // C++ note: You cannot just return an LlcMsg here, or the result is back-converted to LlcMsg. // You must return a pointer, even though they are barely supported by C++. Gotta love it. LlcMsg *LlcFrame::switchFrame() { switch (getFormat()) { case LLCFormat::U: return new LlcFrameU(*this); case LLCFormat::UI: { return new LlcFrameUI(*this); //LlcFrameUI result = LlcFrameUI(*this); //LLCDEBUG << "switchFrame result="<getLlcSapi(),false); uframe.appendUHeader(LlcDefs::UCMD_XID,true); // Leaves append pointer at data. // And I quote: "As an optimisation, parameters confirming the requested // value smay be omitted from the XID response." // So you can just return the xid header. // However, the multitech modem did not accept that, but does succeed // if you send the Full XID response string. //if (gConfig.getNum("GPRS.XID.Full",1)) // There is no reason for this to be an option if (1) { for (int n = 0; n < totlen;) { bool xl = xids.getBit2(n,0); int xidtype = xids.getField2(n,1,5); int xidlen = xl ? xids.getField2(n,6,8) : xids.getField2(n,6,2); n += (xl ? 2 : 1); unsigned value = 0; if (xidlen <= 4) { value = xids.getField2(n,0,8*xidlen); uframe.appendXidItem(xidtype, xidlen,value); LLCWARN("LLC XID"< 4 is the L3 params, just hope we dont get those. LLCWARN("LLC ignoring over-length XID parameter:" <lleWriteRaw(uframe,"xid cmd"); } catch (ByteVectorError) { LLCWARN("over-run error parsing LLC XID command"); } } void LlcFrameU::llcProcess(LlcEntity *lle) { // Note: The U frame exists to send a command via the S and M fields, // which are defined in 04.64 6.4; See enum U_M_Commands // Sec 8.5.4 says we should discard unrecognized if we are in TLL Assigned/ADM state. // Sec 8.8.4 has a table that says the same. // Sec 8.2 describes the P/F bit, says we should return a U command, // so I tried returning DM, but it did not make the multitech modem work. int cmd = getUM(); if (cmd == UCMD_XID) { ByteVector xids = ByteVector(*this); xids.trimLeft(2); // Chop off the U frame header. LLCWARN("LLC XID frame received"<getLlcSapi())); handleXid(lle,xids); } else { const char *cmdname = "?"; switch (cmd) { case UCMD_SABM: cmdname = "SABM"; break; case UCMD_XID: cmdname = "XID"; break; case UCMD_DM: cmdname = "DM"; break; case UCMD_DISC: cmdname = "DISC"; break; case UCMD_UA: cmdname = "UA"; break; case UCMD_FRMR: cmdname = "FRMR"; break; case UCMD_NULL: cmdname = "null"; break; } LLCWARN("LLC U frame ignored"<getLlcSapi())); } } // The checksum has already been chopped off. void LlcFrameUI::llcProcess(LlcEntity *lle) { // This is a data frame. LLCDEBUG("UI::llcProcess"); ByteVector payload(tail(UIHeaderLength)); lle->lleUplinkData(payload); } void LlcFrameUI::writeUIHeader(unsigned wNU /*, bool pf*/) { bool wE = 0; // no encryption. bool wPM = 1; // Checksum FCS is over everything. setField2(controlOffset,0,0x18,5); // UI format tag and unused bits. setField2(controlOffset,5,wNU,9); // frame number. setField2(controlOffset+1,6,wE,1); setField2(controlOffset+1,7,wPM,1); } void LlcEngine::allocSndcp(SgsnInfo *si, unsigned nsapi, unsigned llcsapi) { //LlcEntityUserData *userdatalle = si->mLlcEngine->getLlcEntityUserData(llcsapi); LlcEntityUserData *userdatalle = getLlcEntityUserData(llcsapi); new Sndcp(nsapi,llcsapi,userdatalle); } #if 0==SNDCP_IN_PDP void LlcEngine::freeSndcp(unsigned nsapi) { Sndcp *sndcp = mSndcp[nsapi]; mSndcp[nsapi] = 0; if (sndcp) delete sndcp; // TODO: This is wrong - LlcEntity is by llc sapi, not nsapi. // So I am just commenting it out. // Must reset the LLC state machine also. //LlcEntity *lle = getLlcEntity(nsapi); //if (lle) {lle->reset();} // Better not be 'if' } #endif void LlcEngine::llcWriteHighSide(ByteVector &sdu,int nsapi) { #if SNDCP_IN_PDP PdpContext *pdp = mLleGmm.mSI->getPdp(nsapi); if (!pdp) { LLCWARN("llcWriteHighSide to unconfigured nsapi:"<mSndcp1; #else Sndcp *sndcp = mSndcp[nsapi]; #endif if (sndcp) { sndcp->sndcpWriteHighSide(sdu); } else { assert(0); // not possible because Sndcp and PdpContext allocated/deallocated together. } } void LlcEngine::llcWriteLowSide(ByteVector &bv,SgsnInfo *si) { if (bv.size() < 2) { return; } LlcFrame lframe(bv); int llcsapi = lframe.getSapi(); LLCDEBUG("llcWriteLowSide sapi="<lleWriteLowSide(lframe); } //LlcEntity * SgsnInfo::getLlcEntity(unsigned llcSapi) //{ // return mllcEngine->getLlcEntity(llcSapi); //} LlcEntityUserData * LlcEngine::getLlcEntityUserData(unsigned llcSapi) { return dynamic_cast(getLlcEntity(llcSapi)); } LlcEntityGmm *LlcEngine::getLlcGmm() { return dynamic_cast(getLlcEntity(LlcSapi::GPRSMM)); } void LlcEntity::lleWriteLowSide(LlcFrame &frame) { mVUR++; frame.llcProcess1(this); } void LlcEntity::lleWriteRaw(ByteVector &frame, const char *descr) { gLlcParity.appendFCS(frame); mSI->sgsnSend2MsHighSide(frame,descr,0); //GPRS::DownlinkQPdu *dlpdu = new GPRS::DownlinkQPdu(); //dlpdu->mDlData = uiframe; //LLCDEBUG("llewriteHighSide:"<<(ByteVector)frame); //dlpdu->mDescr = std::string(descr); //mSI->getMS()->msDownlinkQueue.write(dlpdu); } // Write a UI frame for unacknowledged information. void LlcEntity::lleWriteHighSide(LlcDlFrame &frame, bool isCmd, const char *descr) { // Prepend the LLC header; the bv already has room allocated. frame.growLeft(LlcFrame::UIHeaderLength); frame.writeAddrHeader(getLlcSapi(),isCmd); //LlcFrameUI uiframe(frame.begin()); LlcFrameUI uiframe(frame); uiframe.writeUIHeader(mVU++); lleWriteRaw(frame,descr); } void LlcEntityGmm::lleUplinkData(ByteVector &payload) { LLCDEBUG("LlcEntityGmm lleUplinkData"); // This is an l3 message. handleL3Msg(mSI,payload); } // Warning: The MS can send uplink data before attaching or creating pdpcontext, // for example, if the bts is rebooted. void LlcEntityUserData::lleUplinkData(ByteVector &payload) { // okey dokey, this goes to the sndcp. SndcpFrame sframe(payload); unsigned nsapi = sframe.getNSapi(); // The NSAPI is pre-configured by an L3 PDP Context Activation message. // If it does not exist, the MS and BTS are out of sync, possible after a crash, // or invalid RA-Update. Sndcp *sndcp = getSndcp(nsapi); if (sndcp == 0) { if (! mSI->isRegistered()) { // The MS has not done an Attach. // This happens if the BTS comes on and the MS was previously talking to us. // 24.008 Annex G: We can send "ImplicitlyDeattached" and I quote: // "This cause is sent ..., or if the GMM context data related // to the subscription dose (sic) not exist in the SGSN e.g. // because of a SGSN restart." // Update: This does not appear to do the job on the Blackberry. LLCINFO("received packet to detached MS on nsapi="<sndcpWriteLowSide(sframe); } //Sndcp *LlcEntityUserData::getSndcp(unsigned nsapi) { return getSgsnInfo()->mSndcp[nsapi]; } //void LlcEntityUserData::setSndcp(unsigned nsapi, Sndcp*ptr) { getSgsnInfo()->mSndcp[nsapi] = ptr; } #if SNDCP_IN_PDP Sndcp *LlcEntityUserData::getSndcp(unsigned nsapi) { // The pdp will be NULL if the MS sends uplink data before allocating a PdpContext. PdpContext *pdp = mSI->getPdp(nsapi); return pdp ? pdp->mSndcp1 : NULL; } void LlcEntityUserData::setSndcp(unsigned nsapi, Sndcp*ptr) { mSI->getPdp(nsapi)->mSndcp1 = ptr; } #else Sndcp *LlcEntityUserData::getSndcp(unsigned nsapi) { return mSI->mLlcEngine->mSndcp[nsapi]; } void LlcEntityUserData::setSndcp(unsigned nsapi, Sndcp*ptr) { mSI->mLlcEngine->mSndcp[nsapi] = ptr; } #endif // We dont know the length of S SACK format, so return the minimum length. int LlcFrameDump::headerLength() { switch (mFormat) { case LLCFormat::I: return 4; case LLCFormat::S: return 3; case LLCFormat::UI: return 3; case LLCFormat::U: return 2; case LLCFormat::ISack: return 5 + 1 + (mK+1+7)/8; case LLCFormat::SSack: return 3; // Minimum length is probably 4, not 3. case LLCFormat::Invalid: return -1; default: assert(0); } } void LlcFrameDump::llcParseDump() { mK = 0; // headerLength uses mK, so set to 0 for first test here. switch (mFormat) { case LLCFormat::U: mPF = getBitR1(controlOffset,5); mM = getByte(controlOffset) & 0xf; break; case LLCFormat::UI: mNU = getFieldR1(controlOffset,3,9); mE = getBitR1(controlOffset+1,2); mPM = getBitR1(controlOffset+1,1); break; case LLCFormat::ISack: mK = getByte(controlOffset+3) & 0x1f; // Check again, now that we know the real mK if ((int)size() < headerLength()) { mFormat = LLCFormat::Invalid; return; } // Fall through case LLCFormat::I: mA = getBitR1(controlOffset,7); mNS = getFieldR1(controlOffset,5,9); mNR = getFieldR1(controlOffset+1,3,9); mS = getByte(controlOffset+2) & 0x3; break; case LLCFormat::SSack: case LLCFormat::S: mA = getBitR1(controlOffset,6); mNR = getFieldR1(controlOffset,3,9); break; default: assert(0); } } void LlcFrameDump::textHeader(std::ostream &os) { os << "format=" <isPdpInactive(); } unsigned Sndcp::getMaxPduSize() { return mlle->getMaxPduSize(); } SgsnInfo *Sndcp::getSgsnInfo() { return mlle->mSI; } // If we have all the segments for pdu num, send it off. // If force, delete it even if incomplete. void Sndcp::flush(unsigned num, bool force) { OneSdu *sp = &mSegs[num%sMemory]; unsigned i; if (sp->mSegCount) { // We have received the final segment. unsigned totsize = 0; // Do we have all the segments yet? for (i = 0; i < sp->mSegCount; i++) { unsigned size = sp->segs[i].size(); if (size == 0) { break; } // failure. totsize += size; } if (i == sp->mSegCount) { // success. SNDCPDEBUG("flush"<mSegCount)); ByteVector result(totsize); result.setAppendP(0); for (i = 0; i < sp->mSegCount; i++) { result.append(sp->segs[i]); sp->segs[i].clear(); } sp->mSegCount = 0; //mPdp->pdpWriteLowSide(result); getSgsnInfo()->sgsnSend2PdpLowSide(mNSapi,result); //PdpContext *pdp = mlle->getSgsnInfo()->getPdp(mNSapi); //assert(pdp); //pdp->pdpWriteLowSide(result); return; } SNDCPDEBUG("flush still pending"<mSegCount)); } else { // Anything there at all? This is just for a message. for (i = 0; i < 16; i++) { if (sp->segs[i].size()) { SNDCPDEBUG("flush still pending"<segs[i].clear(); } sp->mSegCount = 0; } } int Sndcp::diffSNS(int v1, int v2) { int diff = v1 - v2; if (diff < (int)mSNS/2) diff += mSNS; if (diff > (int)mSNS/2) diff -= mSNS; return diff; } // uplink data from MS comes in here. void Sndcp::sndcpWriteLowSide(SndcpFrame &frame) { // Todo: segment it. unsigned segnum = frame.getSegmentNumber(); unsigned pdunum = frame.getPduNumber(); ByteVector payload(frame.getPayload()); SNDCPDEBUG("uplink packet"<lleWriteHighSide(result,true,"user pdu"); } // downlink data from internet comes in here. // It needs to be segmented and sent to LLC Entity for yet another header. void Sndcp::sndcpWriteHighSide(ByteVector &sdu) { // Set the first byte flags. unsigned flags = mNSapi; flags |= T_BIT; // UNITDATA PDU flags |= F_BIT; // First segment. // Segment the pdu. unsigned segnum = 0; unsigned segsize = getMaxPduSize(); segsize -= 12; // be safe. If you dont do this, the blackberry rejects the packets. for (; sdu.size() > segsize; segnum++) { flags |= M_BIT; // Not last segment. ByteVector seg(sdu.segment(0,segsize)); sndcpWriteSegment(seg,segnum,flags); sdu.trimLeft(segsize); flags &= ~F_BIT; // Not first segment. } flags &= ~M_BIT; // Now it is the last segment. sndcpWriteSegment(sdu,segnum,flags); mSendNPdu = (mSendNPdu+1) % mSNS; } // invert the low width bits of x. static uint32_t revbits(uint32_t x, unsigned width) { x &= ((uint32_t)1<> 1; } return result; } // Pre-compute the CRC divisors for each possible byte. static void genParityTab(uint32_t invGen, uint32_t *tab) { for (int i = 0; i < 256; i++) { uint32_t crc = i; for (int b = 7; b >= 0; b--) { unsigned bit = crc & 1; crc >>= 1; if (bit) { crc ^= invGen; } } tab[i] = crc; } } Parity32::Parity32(uint32_t generator, unsigned width, bool invertFirst) { mMask = (((uint32_t)1<> 1) | (1<<31); } else { mInvertedGenerator = revbits(mMask & generator,24); } genParityTab(mInvertedGenerator,mTab); } uint32_t Parity32::computeCrc(unsigned char *str, int len) { uint32_t crc=mInitialRemainder; unsigned char *bp = str, *ep = str + len; while (bp < ep) { crc = (crc >> 8) ^ mTab[(crc ^ *bp++) & 0xff]; } return (~crc) & mMask; // As a comment, this is the identical algorithm to the above, without the table lookup: /*** for (int l = 0; l < len; l++) { crc = crc ^ str[l]; for (int b = 7; b >= 0; b--) { unsigned bit = crc & 1; crc >>= 1; if (bit) { crc ^= lsbgen; } } } ***/ } uint32_t Parity32::computeCrc(ByteVector &bv) { return computeCrc(bv.begin(),bv.size()); } extern "C" { int gprs_llc_fcs(uint8_t *data, unsigned int len); }; void LlcParity::appendFCS(ByteVector &bv) { uint32_t fcs = computeCrc(bv); // append 24-bit fcs LSB first. bv.appendByte(fcs&0xff); bv.appendByte((fcs>>8)&0xff); bv.appendByte((fcs>>16)&0xff); // Double check: #if 0 uint32_t oldcrc = gprs_llc_fcs(bv.begin(),bv.size()-3); if (fcs != oldcrc) { printf("CRC ERROR: old=%d new=%d\n",oldcrc,fcs); } else { printf("CRC matches\n"); } #endif } // Check the FCS in the last 3 bytes of bytevector. bool LlcParity::checkFCS(ByteVector &bv) { unsigned len = bv.size(); uint32_t fcs = (bv.getByte(len-1)<<16) | (bv.getByte(len-2)<<8) | bv.getByte(len-3); uint32_t computedFCS = computeCrc(bv.begin(),len-3); return fcs == computedFCS; } LlcParity gLlcParity; // The one and only parity generator needed. }; // namespace